Commit graph

212 commits

Author SHA1 Message Date
nns 68bfb582e7 feat(web): Z-отчёт смены — разбивка по товарам + предупреждение о >24ч (п.1/2)
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Веб Z-отчёт показывает byProduct (товар/продано/выручка/возвраты) и баннер «смена
открыта дольше 24ч». Печать уже была (window.print()).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 06:48:00 +05:00
nns fd03700a58 feat(web): кнопка «Создать возврат» на проведённой приёмке (п.5 create-based-on)
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Проведённая приёмка → «Создать возврат» вызывает from-supply (преднабор строк) и
открывает черновик возврата поставщику. Вместе с секцией оплат приёмки (supply→
payment) и ссылками-основаниями в DTO — развивает «создать на основании».

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 06:41:03 +05:00
nns 524f8f41b1 feat(money): акт сверки с поставщиком — печать/PDF (п.7)
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
GET /api/money/reports/supplier-reconciliation?supplierId=&from=&to=: начальное
сальдо (до from), движения периода (приёмка +долг, возврат/оплата −долг) с бегущим
сальдо, конечное сальдо. Веб-страница /money/supplier-debts/:id/reconciliation —
печатная форма акта (window.print()/PDF) + кнопка «Акт сверки» в долгах поставщикам.
Integration-тест: приёмка как debit, конечное сальдо.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 06:39:08 +05:00
nns 18752497a8 feat(web): статусы DocStatusBadge на всех списках документов + кнопка «Сгенерировать» ШК
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
П.9: DocStatusBadge (Черновик/Проведён с подсказкой) раскатан на Оприходования,
Списания, Перемещения, Возвраты поставщикам, Отгрузки (было: приёмки/продажи/инвент.).
П.4: в карточке товара рядом с полем штрихкода — кнопка «Сгенерировать» (внутренний
EAN-13). Группа по умолчанию — «Все товары», единица — «шт» (уже было).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 06:32:58 +05:00
nns 0f1574df64 feat(web): привычные названия денег (ПКО/РКО) + прибыль на главной + статусы с подсказкой
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
П.7: типы денег названы привычно — «Приход наличных (ПКО)», «Расход наличных (РКО)»,
«Входящий/Исходящий платёж», «Перевод между счетами».
П.8: блок «Прибыль за сегодня/месяц» на дашборде (во всех режимах, /api/dashboard/margin),
с пометкой «приблизительно; точно после FIFO».
П.9: общий DocStatusBadge (Черновик/Проведён/Завершена) с tooltip «что это значит»,
применён на списках Приёмок/Продаж/Инвентаризаций.
П.6: проверено — Оприходование/Списание в простом режиме не прячутся (отдельные пункты).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 06:04:18 +05:00
nns 560d52e8cd feat(notifications): Web Push (VAPID) — браузерный push о минусах
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Канал Web Push для центра уведомлений (item 5). Ключи VAPID генерируются один раз
и хранятся в platform_settings (без ручной настройки секретов); публичный отдаётся
фронту. Таблица web_push_subscriptions (Phase42) + /api/web-push/{public-key,
subscribe,unsubscribe}. Отправка через web-push-csharp из фонового джоба минусов
(best-effort, протухшие подписки чистятся). SW: обработчики push/notificationclick
(am-v3→am-v4), клик открывает нужный путь. Кнопка «Вкл. push» в колокольчике.
Работает в т.ч. в iOS Safari для PWA на домашнем экране.

Тесты: integration — публичный ключ стабилен, подписка идемпотентна, 400/unsubscribe
(4/4 группы); unit 299/299.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 03:49:32 +05:00
nns 09ee0562c2 fix(web): центр уведомлений открывается вправо от колокольчика (не обрезается)
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Колокольчик в футере сайдбара слева — popover с right-0 уходил за левый край
экрана. left-0 раскрывает его вправо в область контента.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 03:06:45 +05:00
nns 29a951a6ff feat(notifications): центр уведомлений + агрегация/эскалация минусов + каналы
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Персистентные уведомления (таблица notifications, Phase41) + REST
/api/notifications (список/счётчик/read/read-all) → центр уведомлений в вебе
(колокольчик, API-backed) и мобильном API.

Фоновый джоб NegativeStockNotificationJob (cron */5): агрегирует новые минусы
в ОДНО уведомление на склад (дебаунс по NotifiedAt + интервал орг., по умолч.
15 мин), уважает тихие часы (Asia/Almaty); эскалация просроченных (>N часов,
по умолч. 24) владельцу/админам. Получатели — админы, владелец, кладовщики
(право InventoryEdit). Каналы: SignalR (живой колокольчик), Telegram (владельцу),
push-заглушка.

Настройки организации: NegStockNotify{Enabled,IntervalMinutes,QuietFrom/ToHour,
EscalateHours} + GET/PUT /api/organization/settings + секция в настройках веба.
Блок «Просроченные минусы» на дашборде.

Тесты: unit тихих часов 3/3; integration — джоб создаёт уведомление админу,
дебаунс, read-all 3/3 (вся группа NegativeStockTests зелёная).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-09 02:56:59 +05:00
nns 9ba1edb236 feat(stock): разрешение продажи в минус + предупреждения об отрицательных остатках
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Решение владельца 08.10: продавать/списывать в минус РАЗРЕШЕНО, но с постоянным
предупреждением и требованием провести инвентаризацию.

- Снята блокировка «недостаточно остатка» (409 oversell): розница, касса (POS-синк),
  опт-отгрузка, списание, перемещение, возврат поставщику, отмена приёмки. Деньги
  (минус счёта) — по-прежнему запрещены (другое правило).
- NegativeStockAlert (сущность + миграция Phase39): создаётся/обновляется при уходе
  остатка < 0 (единая точка — StockService), авто-закрытие при возврате ≥ 0. Частичный
  уникальный индекс: один открытый алерт на (org, store, product). Причина: тип/номер
  документа/автор/время.
- API /api/negative-stock: список (с подсказкой «возможно, не проведена приёмка П-…»),
  /count (для баннеров), /recount (черновик инвентаризации по минусам склада). Отчёт
  «Остатки» — фильтр negativeOnly.
- Веб: постоянный баннер «N товаров в минусе — нужна инвентаризация» (сворачивается,
  крестиком не закрыть) + «Пересчитать эти товары»; в остатках — красная пометка и
  фильтр «Только минусы».
- Тесты: integration 2/2 (минус разрешён → алерт → recount → авто-закрытие; фильтр);
  full-chain шаг 9/13 переписаны (oversell/отмена приёмки → проведено + предупреждение).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 02:18:29 +05:00
nns ab416d720c feat(inventory): отмена результатов не удаляет проведённые документы
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Было: unpost при проведённых связанных Оприходовании/Списании молча снимал их с
проведения и УДАЛЯЛ (204, номера исчезали). Хуже МойСклада, теряется история.

- unpost при ПРОВЕДЁННЫХ связанных → 409 с номерами О-/С- и текстом «Сначала
  отмените результаты». Если связанные только черновики — удаляем черновики
  (не влияли на остатки) и возвращаем инвентаризацию в черновик.
- Новое POST /{id}/cancel-results (InventoryEdit + EnterEdit/LossEdit): снимает
  связанные Оприходование/Списание с проведения, НЕ удаляя (остаются черновиками,
  видны в «Связанных документах», истории, аудите); остатки возвращаются; для
  Оприходования — гард на уход остатка в минус. Аудит InventoryCancelResults.
- Веб: кнопка «Отменить результаты» (при проведённых связанных) + диалог со
  списком документов и влиянием на остатки; «Вернуть в черновик» переформулирован.
- docs/mobile-api.md: unpost больше не удаляет проведённые (409), добавлен cancel-results.
- Тесты: integration (409 + cancel-results оставляет черновики + повторное
  проведение) + full-chain шаг 10.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:51:00 +05:00
nns 6557ac34ec test(menu-walk): лингва-проверка UI + фикс «low-stock» в настройках
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
- stage-ui-menu-walk: assertNoUiEnglish — узкий denylist хромовых строк
  (Dashboard / «Low-stock alerts» / live / offline / Live-обновл), пропуск
  документации (/help, /whats-new, /admin/diagnostic), где латиница легитимна
  (клавиши, код-идентификаторы, бренды). Прогон admin: 1 passed, 0 ошибок.
- Настройки Telegram: «…топ-3 товара, low-stock…» → «…низкий остаток…» (ru.json
  + OrganizationSettingsPage) — единственная оставшаяся видимая латиница в UI.
- Галерея сайта и прогресс обновлены.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:30:37 +05:00
nns d20cf15ca2 fix(ui): правки по ревью скриншотов — i18n, валюта, дубль групп, касса, фасовки
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
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (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
1. Английские строки в админке → русский: ru.json dashboard.title «Dashboard»→
   «Сводка», common.live/offline «live/offline»→«онлайн/офлайн»; дашборд-виджеты
   defaultValue «Low-stock alerts»→«Низкий остаток», «Live-обновление»→«Обновляется
   в реальном времени».
2. Каталог: системная группа «Все товары» больше не дублирует корень дерева
   (ProductGroupDto.IsSystem + ProductGroupTree прячет системные группы).
3. Валюта: розничная цена в каталоге теперь «₸», а не «KZT» (символ вместо кода).
4. Касса на сайте: вместо HTML-макета — НАСТОЯЩИЙ экран Avalonia-кассы
   (--screenshots) с витринными данными «Магазин у дома», кассир Виктория Ким, с
   логотипом; убран префикс «Магазин » в шапке (не было двойного «Магазин Магазин»).
   HTML-макет удалён из публичного пайплайна.
5. Витрина: фасовка/единица товара выводится из названия (кг→весовой, иначе
   штучный) — нет «Авокадо 1шт весовой» / «Бешбармак 1кг штучный».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 22:53:28 +05:00
nns b4bd0e41d4 feat(inventory): веб-UI по модели МойСклада + доки
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
- InventoryEditPage: «Завершить подсчёт» вместо «Провести»; на завершённой — кнопки
  «Создать оприходование излишков» / «Создать списание недостачи» (блокируются, если нет
  разницы или документ уже создан) + блок «Связанные документы» + «Вернуть в черновик».
- InventoriesPage: статус «Завершена»; колонка «Результат» (опр./спис. № или «не применено»).
- Enter/LossEditPage: «На основании инвентаризации №…» со ссылкой.
- types.ts: InventoryStatus Draft/Completed; related в InventoryDto; результат в списке;
  inventoryDocId/Number в Enter/Loss DTO.
- docs/inventory-moysklad-reference.md (сверка), inventory-model-progress.md,
  supply-inventory.md («Как провести инвентаризацию», 5 шагов), mobile-api.md (compat post/unpost).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:34:18 +05:00
nns 466e546ae2 rebrand: кэш-бастинг логотипа (старое лого залипало в SW/кэше)
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 Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (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
Причина: SW веб-админки прекэшировал /logo.svg и отдавал stale, CACHE_VERSION не
менялся при ребрендинге; публичный сайт отдавал лого/favicon без Cache-Control.

- sw.js: CACHE_VERSION fm-v2 → am-v3 (+ASSET_VERSION am3); activate чистит старые
  кэши, skipWaiting+clients.claim; прекэш бренд-ассетов версионирован; бренд в
  комментариях → ALL MARKET.
- ?v=am3 на нехэшированных бренд-ассетах: index.html, manifest, Logo.tsx,
  публичный Logo.astro + BaseLayout favicon.
- nginx web: index.html + бренд-ассеты + manifest → no-cache; /assets/* → immutable.
- nginx public: html + лого/favicon → no-cache; /_astro/* → immutable.

Проверено Playwright: старый клиент (SW fm-v2 + лого Food Market) → после выкладки
+ 1 перезагрузки кэш am-v3, fm-v2 удалён, лого ALL MARKET. Заметка на будущее про
очистку API_CACHE при logout — в прогресс-доке.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:47:41 +05:00
nns 026de9f262 rebrand: безопасность входа + тост 404 на странице входа (по ревью Mac-Claude)
БЕЗОПАСНОСТЬ:
- Страница входа больше НЕ префилит e-mail/пароль dev-админа и не показывает
  подсказку «Dev admin: …» — только в import.meta.env.DEV на localhost
  (на стенде/проде поля пустые).
- DevDataSeeder: пароль SuperAdmin/демо-админа ТОЛЬКО из секрета
  (Seed:SuperAdminPassword / env SEED_SUPERADMIN_PASSWORD). Дефолт «Admin12345!»
  допустим лишь в Development. На stage/prod (ASPNETCORE_ENVIRONMENT=Production)
  без секрета аккаунт с известным паролем НЕ создаётся (warning в лог).
- stage-compose: проброшен Seed__SuperAdminPassword (без дефолта).
- (Пароль существующего SuperAdmin на стенде сменён вручную на сложный и сохранён
  в ~/.fm-watchdog/stage-superadmin-password; quick-login по sentinel/master цел.)

ТОСТ 404:
- StageBanner и QuickLoginBlock пробуют необязательный /api/dev/quick-login/accounts
  через raw fetch (минуя axios-интерсептор) — на проде/без API 404 больше не
  показывается глобальным тостом. На странице входа тостов ошибок нет.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:07:39 +05:00
nns 57f36d8dee rebrand: веб-админка ALL MARKET (лого, favicon, PWA, тексты)
- Логотипы: logo.svg/logo-light.svg/favicon.svg → готовые файлы ALL MARKET;
  добавлены apple-touch-icon.png (180), favicon-32.png, icon-192/512.png (PWA maskable).
- index.html: <title>ALL MARKET, apple-touch-icon → png, favicon-32.
- manifest.webmanifest: name/short_name ALL MARKET, PNG-иконки 192/512.
- Logo.tsx: alt ALL MARKET, высота h-8; на экранах входа/восстановления — h-12 (охранное поле).
- Онбординг, SuperAdmin-настройки (имя отправителя/тест), offline.html → ALL MARKET.
- theme_color и цвета интерфейса не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:34:27 +05:00
nns 9dc033d07a fix(money): кассир открывает смену без доступа к модулю «Деньги» (403)
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Инцидент menu-walk 2026-10-08: demo-cashier получал 403 на
GET /api/money/accounts — страница смен (/money/shifts) дёргала его для выбора
кассы при открытии смены, но эндпоинт под MoneyView, а у кассира MoneyView=false
(«кассир не видит денег»), только CashShiftOperate.

- Новый узкий GET /api/money/shifts/accounts (RequiresPermission
  "MoneyView,CashShiftOperate"): только активные наличные кассы + эквайринг,
  без остатков и банковских счетов. Кассиру достаточно для выбора кассы/эквайринга
  при открытии смены, но денег он по-прежнему не видит.
- Веб: CashShiftsPage использует useShiftAccounts() вместо useMoneyAccounts(true).
- Регресс-тесты: кассир 200 на /shifts/accounts (только нал+эквайринг) и 403 на
  /money/accounts; /api/me permissions для кассира (advanced-only скрыт).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:28:36 +05:00
nns 2a4ba14d38 feat(auth): эффективные права пользователя в /api/me (единый источник)
- RolePermissions.Effective(role, isSuperAdmin, isAdmin, advancedMode) — флаги с
  учётом роли и простого/расширенного режима; AdvancedOnly — единый список
  advanced-only прав (зеркалит [RequiresAdvancedMode]).
- /api/me теперь отдаёт permissions (имя→bool). Мобильное и веб скрывают одно и то же.
- Веб: хук useCan() читает me.permissions (раньше гейтил только по именам ролей).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 05:39:37 +05:00
nns cbf92dca3b feat(web): карточка скачивания кассы на странице «Кассы»
Запрашивает GET /api/pos/v1/version и показывает версию + кнопки «Скачать для
Windows / для Linux» (ссылки из конфигурации сервера, секция Pos). Если ссылки
не настроены — подсказка администратору.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:58:54 +05:00
nns 009a9d8a50 fix(web): EAN-13 контрольная цифра, валюты в приёмке сканированием, поиск товара на накладной
1) digitsChecksum считал веса слева — для 12-значного тела (EAN-13) чётность
переворачивалась и контрольная цифра была неверной; теперь справа налево
(EAN-8/UPC-A не изменились). 2) SupplyScanPage читал /api/catalog/currencies как
массив, а это PagedResult {items} → список валют пуст (нет KZT). 3) InvoiceReviewPage
слал ?q=, а API ждёт ?search= (PagedRequest.Search).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 02:10:11 +05:00
nns 830cd73ee0 feat(money/web): UI раздела «Деньги» — счета, платежи, смены, долги, отчёт (п.7)
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
- Раздел «Деньги» в sidebar: Отчёт/Счета и кассы/Платежи и ордера/Смены/
  Долги поставщикам. Админ — всё; кассир — только смены; кладовщик — ничего.
- Страницы (src/pages/money/): MoneyAccountsPage, ExpenseItemsPage,
  MoneyTransactionsPage (+ переводы), CashShiftsPage (open/close/внесения/
  выплаты/Z-отчёт), SupplierDebtsPage (+ корректировки), MoneyReportPage
  (остатки + движение за период).
- Приёмка: SupplyPaymentSection (статус/оплачено/остаток + «Оплатить»
  наличными/с банка, частичная оплата); в списке приёмок — колонка и фильтр
  статуса оплаты.
- src/lib/money.ts (enum'ы/DTO/лейблы/lookups), типы SupplyDto/SupplyListRow
  +payedSum/+paymentStatus, роуты в App.tsx, i18n ru/en.

Без AdvancedModeGuard — модуль для простого режима. tsc -b + eslint зелёные.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-07 21:59:32 +05:00
nns e45600df4b feat(invoices+catalog): порт улучшений 06.10.2026 из sklad
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Основное:

* Program.cs: SuppressImplicitRequiredAttributeForNonNullableReferenceTypes=true
  + InvalidModelStateResponseFactory → ValidationProblemDetails + Serilog Warning.
  Пустые необязательные строковые поля в InvoiceLine (Note, BarcodeSource и т.п.)
  больше не роняют запрос в 400. humanizeError на фронте уже умеет этот формат.
* InvoiceModels.cs: Name/Source/FilePath → string?.
* Validators.cs: InvoiceSaveLinesValidator (Lines NotNull), InvoiceApplyValidator
  (Guid.Empty на Store/Currency = ошибка, SupplierId опционален).

Служебный «Поставщик не указан» (адаптация sklad §11 под multi-tenant):

* Phase32b миграция добавляет Counterparty.IsPlaceholder bool + partial index
  по IsPlaceholder=true.
* InvoicesController.Apply: supplierId опционален; если не указан —
  ResolvePlaceholderCounterpartyAsync ищет/создаёт одного служебного per org.
  Имя из накладной в комментарий документа НЕ пишется.
* CounterpartiesController: служебный всегда последним в List сортировках,
  Update и Delete при IsPlaceholder → 409 Conflict.
* CounterpartyDto.IsPlaceholder + Mapster-config обновлены.
* Автоподбора/автозаведения контрагентов нигде не было — специально проверили.

Applied-накладная readOnly (§10):

* InvoiceDetailDto: добавлены supplyNumber + processedAt.
* InvoiceReviewPage: applied-режим — плашка «Черновик приёмки № … создан …»
  со ссылкой на приёмку, шапка и строки readOnly, скан-блок и apply-кнопка
  скрыты. После успешного apply — invalidateQueries, экран сразу readOnly.

Текст ошибки apply (§5) и список накладных (§6):

* ApplyDialog: error → detail → title → (код), __silent на axios.
* Invoices list: parseServerDate подшивает «Z» если его нет, «Загружена DD.MM.YY HH:MM»
  в бейдже статуса (видимо в десктоп-таблице и в мобильной карточке — у нас общая).

Тесты + проверки:

* tests/food-market.IntegrationTests/InvoiceApplySklad0610Tests.cs — 5 кейсов:
  SaveLines со всеми null, apply без поставщика с idempotency по placeholder'у,
  Delete/Update placeholder → 409, applied detail содержит processedAt +
  hasFiles=false, apply с Guid.Empty storeId → ValidationProblemDetails.
  Все 5 ✓.
* stage retest на https://test.admin.food-market.kz: upload+SaveLines(null-fields)+
  apply(без поставщика)+apply(с ИП Бегимбаев)+DELETE placeholder → весь flow
  зелёный, служебный один на орг, hasFiles=false после apply.
* quality-watchdog step 9 (menu-walk): 3/3 passed (3.7m).
* Фото-накладная через Claude CLI не гонял — пайплайн apply→DeleteStored
  закрыт через hasFiles=false в тестах и реальный stage smoke.

docs/supply-inventory.md — шаг 7 расписан под необязательного поставщика,
новый шаг 8 (applied → readOnly), миграция Phase32b в таблице «Файлы».

docs/sklad-0610-progress.md — финал 9/9 с подробным журналом решений.

Пред-существующие (не трогал):

* OrgAuditLogTests.Counterparty_update_diff_contains_before_after → 403 от
  [RequiresAdvancedMode] на /api/admin/audit-log (commit 921bca0, s29);
* ApiReferenceDocsJobTests.Scans_endpoints_with_doubly_nested_generic_return —
  регекс-тюнинг доков.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-07 19:50:03 +05:00
nns 6b0fc68f77 feat(invoices+inventory): порт улучшений 05.10.2026 из sklad
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Диапазон коммитов f455fa9..b7eb637 + 3ced706 (handoff).

Backend (src/food-market.api/Infrastructure/Invoices):
- Ean.cs — контрольная цифра EAN-8/13/UPC-A/GTIN-14 (ловит одну ошибку в цифре).
- TableReader.cs — своя читалка .xlsx/.csv без библиотек, UTF-8/1251,
  .xls → понятная ошибка; TableInvoiceParser — шаблон накладной.
- InvoiceModels: +Photo/BarcodeSource/Note в InvoiceLine, PhotoHint,
  IInvoiceExtractor.ExtractPhotosAsync.
- InvoiceMatcher.FindByBarcode — для photo-matching.
- InvoiceProcessor: ветки photos/excel/vision, MatchFromPhoto с EAN-проверкой
  (barcode_source="read" → валидация), SweepAbandonedPhotosAsync раз в час
  убирает папки needs_review старше 24ч; DeleteStored (owner-policy: НЕ копить).
- ClaudeCliInvoiceExtractor: ExtractPhotosAsync + BuildPhotosPrompt +
  FromPhotosJson (barcode_source → BarcodeSource, comment → Note, unmatched_photos
  → Warnings); дефолт модели claude-sonnet-5-5, PhotoTimeoutSeconds=600.

Backend (Controllers):
- POST /purchases/invoices/upload-photos (≤80 файлов, 60 МБ) +
  GET /purchases/invoices/{id}/photo/{n}; Apply/Delete убирают папку (не только файл).
- InvoiceDetailDto +Source/+HasFiles — клиент не ломится за 404 миниатюрами.
- POST /inventory/inventories/import — xlsx/csv → {found, unknown}, повторы ШК
  суммируются, документ не создаётся.
- Program.cs: UseResponseCompression + GzipCompressionProvider для application/json.
- InventoriesController.mobile-catalog: weak ETag + If-None-Match → 304;
  Cache-Control private max-age=60. Замер 31k: JSON 3.60 МБ → gzip 0.98 МБ.

Frontend:
- lib/shots.ts — ZXing TRY_HARDER на 1x+1600; compress JPEG 0.85 до 2400.
- components/PhotoSession.tsx — полноэкранная пачка (камера + готовые фото).
- components/CameraScanner.tsx: onCodeRef + эффект [], камера не закрывается
  после каждого скана.
- pages/InvoicesPage.tsx — четыре способа: камера одиночными, файл (xlsx/csv/img/pdf),
  камера многостраничная, фото-приёмка (камера или готовые фото) + «Отправить ещё раз».
- pages/InvoiceReviewPage.tsx — автосохранение 700 мс + индикатор; row-picker панель
  «в какую строку?» если fuzzy-match неуверен; linesRef; глобальный keydown; миниатюра
  снимка у строки; пометки Note.
- pages/SupplyScanPage.tsx + InventoryMobilePage.tsx — глобальный keydown.
- InventoryMobilePage: поле сканера на виду + импорт Excel/CSV.
- docs/supplier-invoice-template.xlsx — шаблон для поставщиков (9-я строка заголовки).

Хранение файлов (owner-policy 2026-10-05): фото НЕ копим.
- Обычная загрузка → удаление сразу после разбора.
- Фото-приёмка → удаление при Apply/Delete/24h abandoned.
- KeepFilesDays удалён из флоу (конфиг-ключ игнорируется).

Тесты (unit, зелёные):
- EanTests (6): валидные/невалидные/one-digit error.
- TableReaderTests (5): xlsx cells, шапка «Поставщик/Номер/Дата», CSV UTF-8+BOM,
  CSV Windows-1251, .xls friendly error.
- ClaudeCliInvoicePhotosParseTests (4): decoded проходит, source=null чистит Barcode,
  unmatched_photos → Warnings, comment → Note.
- InvoicePhotoRetentionTests (3): DeleteStored файл/папка/идемпотентность.

Preexisting ApiReferenceDocsJob (Sprint 28) failure не связан с портом.

Прогресс: docs/sklad-port-20261005-progress.md. Пользовательская инструкция:
docs/supply-inventory.md обновлена.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-06 00:02:13 +05:00
nns f8480b06ae fix(hotfix): TelegramSection — gate GET /api/organization/telegram/status на advancedMode
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Пропустил в первом коммите: OrganizationSettingsPage в простом режиме
дёргал /api/organization/telegram/status, который [RequiresAdvancedMode].
Токсичный тост силен silent-GET-403 политикой (api.ts), но menu-walk
e2e всё равно ловил 4 сетевых 403 для demo-admin → fail. Чиним у
источника: не фетчим если not advanced.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-05 00:32:41 +05:00
nns f434e87d9b fix(hotfix): invoice500 + фоновые 403 на reload + regression e2e
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Owner caught on stage (2026-06-07 18:57):
1) POST /api/purchases/invoices/upload → 500: migration Phase31a создала
   invoice_uploads.UpdatedAt как NOT NULL, но конвенция проекта — DateTime?
   nullable + стамп только для Modified в AppDbContext.StampTimestamps.
   EF на INSERT отправлял UpdatedAt=NULL → 23502. Фикс: Phase31b DROP NOT NULL
   + Phase31b для invoice_aliases.
2) Фоновые 403 → тост «Нет доступа» при F5 на любой странице:
   - SavedPresets фетчил /api/user/presets (RequiresAdvancedMode) из любой
     страницы с фильтрами в простом режиме → 403;
   - useOnboardingProgress фетчил /api/organization/employees (Admin only)
     для кладовщика/кассира на '/' → 403.
   Фикс:
   - api.ts: GET-403 silent, тост только на действия юзера (POST/PUT/DELETE);
   - SavedPresets gated на advancedMode (useMe + enabled);
   - useOnboardingProgress canSeeEmployees-gate.
3) tests/food-market.IntegrationTests/InvoiceUploadHttpTests.cs — HTTP-прогон
   POST /upload через Testcontainers-Postgres (дыра: s31 pipeline tests
   проверяли matcher, но ни разу не инсертили в invoice_uploads).
4) tests/e2e/scenarios/stage-ui-menu-walk.spec.ts — regress: каждая демо-роль,
   каждый пункт меню, F5, assert 0 unexpected 4xx/5xx + 0 error-тостов +
   0 console.error. В quality-watchdog step 9 с 4-часовой каденцией.
5) tests/e2e/scenarios/stage-ui-invoice-upload.spec.ts — demo-keeper
   загружает реальное фото накладной (tests/fixtures/invoice-sample.jpg,
   сгенерирована Playwright-ом из HTML, 1800×2400 JPEG) → ждёт recognition
   → экран проверки. При не-настроенном Claude CLI тест fail'ится с ясным
   сообщением (это тоже баг).

Прогон локально 4/4 (InvoicePipelineTests 2/2 + InvoiceUploadHttpTests 2/2).
docs/hotfix-invoice500-403-progress.md — ход работы 1-4/5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-05 00:21:41 +05:00
nns b82adc414c feat(menu): новые экраны s31 в sidebar+Cmd+K+Cmd+J + split-кнопки, orphan-audit
Три новых роута из Sprint 31 были достижимы только по прямому URL —
в sidebar/командной палитре их не было. Добавлено:

- AppLayout: «Накладные (по фото)» (/purchases/invoices) в разделе
  «Закупки», «Инвентаризация с телефона» (/inventory/mobile) в
  «Остатках» — для Admin и Storekeeper, видно в простом режиме;
  i18n ru + en.
- SplitButtonMenu — переиспользуемая dropdown-кнопка «Новая…».
  SuppliesPage: вручную / по фото / сканированием. InventoriesPage:
  вручную / с телефона.
- CommandPalette (Cmd+K): новые static-pages invoices, supply-scan,
  inventories (был пропущен), inventory-mobile.
- QuickActionsPalette (Cmd+J): invoice-new, supply-scan,
  inventory-mobile.
- Параллельно всплыл сирота /admin/diagnostic — добавили ссылку
  в sidebar под Admin'ом.

Playwright stage-ui-menu-links.spec.ts: demo-keeper + demo-admin,
desktop 1280×800 + mobile 375×667 — кликами с главной до всех
трёх новых экранов, скриншоты в reports/. Негатив для demo-cashier
проверяет, что ему эти пункты не видны.

Orphan-audit: tests/scripts/menu-orphans-audit.mjs парсит App.tsx и
диффит против фактических точек входа (sidebar/Cmd+K/Cmd+J/split-
кнопок). После всех правок — 0 сирот (отчёт docs/menu-orphans-report.md).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 22:50:14 +05:00
nns 43cf9ea040 fix(s31): уберём неиспользуемые импорты — ломали docker-билд web
Docker web build использует 'tsc -b' (noUnusedLocals=true), который на
dev-машине проскакивал через tsc --noEmit. Три unused:
- InventoryMobilePage: countRows (из offlineCatalog)
- SupplyScanPage: AlertTriangle, Plus (lucide-react)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 21:53:10 +05:00
nns f42329281c feat(s31): мобильная инвентаризация — offline-каталог, drift-check, расхождения (4+5+6/8)
Backend дополнения (InventoriesController):
- GET /mobile-catalog?groupId= — компактные тройки [barcode, productId,
  name] для оффлайн-работы. Фильтр по группе = частичная инвентаризация.
- POST /{id}/add-line — scan-mode: +1 к существующей строке или новая,
  bookQty подтягивается из текущего Stock (пункт 6: без ручного
  «Пересчитать»).
- GET /{id}/book-drift — список позиций, где текущий Stock != сохранённого
  BookQty. UI показывает перед Post, чтобы было видно что учётный поплыл
  (продажи/приёмки с момента создания). Пункт 6 целиком.

Существующий Create уже заполняет bookQty из Stock (строки not passed →
подтягивает весь склад; строки passed → подтягивает book по каждой).
Update тоже auto-fill'ит для новых строк.

Web (lib/offlineCatalog.ts): IndexedDB wrapper (DB=fm-inventory,
STORE=catalog, META=meta). saveCatalog/findByBarcode/catalogMeta/
countRows. Порт из sklad'овского db.ts.

Web (InventoryMobilePage.tsx) — три stage'а в одном файле:
- setup: склад + опц. группа + 'Загрузить каталог на телефон'.
- scan: Камера <CameraScanner /> или аппаратный сканер; скан → +1 к
  существующей строке; неизвестные → в отдельный список unknown с
  предупреждением; офлайн-плашка; localStorage сохраняет rows+docId.
- review: карточки с book/факт/дельта/дельта*себестоимость + фильтр
  'только расхождения'; drift-плашка если учётный поплыл;
  'Провести' → POST /post.

Routes: /inventory/mobile. tsc зелёный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 21:40:58 +05:00
nns 05e7fdecef feat(s31): CameraScanner + scan-mode приёмки с last-purchase price (3/8)
CameraScanner (components/CameraScanner.tsx) — порт из sklad'a @zxing/
browser + @zxing/library. Непрерывное сканирование: окно не закрывается
между сканами, EAN13/EAN8/UPC/Code128/ITF. Подавление повтора 1.5с (в
кадре товар распознаётся десятки раз в секунду). Вибрация 40мс + flash
зелёный/красный + анимация scanPop/scanRing.

SupplyScanPage (/purchases/supplies/scan):
- Выбор поставщика (search по /counterparty-search), склада, валюты.
- Кнопка 'Сканировать камерой' → <CameraScanner /> на полный экран.
- Alt: аппаратный сканер — <input> в details-блоке, Enter = +1.
- Каждый скан: GET /product-by-barcode → если нашёлся, +1 к
  существующей строке или новая сверху. Цена подставляется last-
  purchase-price (из последней Posted supply-line по этому товару).
- Неизвестный штрихкод: feedback 'Нет в каталоге', вибрация ошибки.
  Товар создавать — отдельный переход на /products/new (future wiring).
- Прогресс сохраняется в localStorage ('fm.supply-scan.rows') —
  перезагрузка не теряет; очищается после успешного сохранения.
- Save → POST /api/purchases/supplies (существующий endpoint), статус
  Draft; редирект в /purchases/supplies/:id для проверки/проведения.

InvoicesController.ProductByBarcode теперь возвращает ещё и
lastPurchasePrice (из последней Posted supply-line).

@zxing/browser@0.1.5 + @zxing/library@0.23.0 добавлены в deps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 21:36:41 +05:00
nns 693832f51c feat(s31): web — список накладных + экран проверки + apply → Supply (2/8)
InvoicesPage: карточки загрузок со статусом (бейдж + иконка),
поллинг каждые 3с пока есть pending/parsing (останавливается на
needs_review/applied/failed). Две кнопки загрузки:
- 'Снять камерой' (<input capture="environment" accept="image/*">)
- 'Загрузить файл' (image/* + pdf, multi-select)
Mobile-first: кнопки большие, grid 2-col; на desktop растёт.

InvoiceReviewPage: карточками-строками (телефон читается, desktop
масштабируется). Редактируемые поля qty/price, авто-сумма.
- Шапка: supplier, №, дата, сумма (editable). Предупреждение если
  сумма строк отличается от «Итого» больше чем на 1.
- Общий сканер (input Enter) → в первую несопоставленную строку.
- Для несопоставленной строки — 'Найти товар' (debounced search
  по /api/catalog/products?q=) с ссылкой 'Создать товар' если
  ничего не нашлось.
- Save → PUT /lines (пишет alias'ы автоматически).
- Apply → ApplyDialog: выбор поставщика (search с БИН),
  склад (default main), валюта (default KZT). POST /apply →
  редирект в /purchases/supplies/:id.

Routes добавлены в App.tsx: /purchases/invoices и /purchases/
invoices/:id.

tsc зелёный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 21:33:02 +05:00
nns e1718ec972 feat(s30): sentinel pwd для SPA — реальный мастер-пароль только в server-env
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
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (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
Координатор отротировал мастер-пароль стенда: теперь значение живёт в
~/.fm-watchdog/stage-shared-password на dev-vm (не коммитится) и в
QUICK_LOGIN_MASTER_PASSWORD в stage-env на prod-vm. Старый дефолт '123456'
на обоих сторонах убран — он мог вводить в заблуждение и был записан в
бандл.

Клиент-серверный контракт:
- Server (AuthorizationController.IsMasterPassword) принимает ДВА пароля
  как мастер-ключ: (a) QuickLoginOptions.MasterPassword (константное
  сравнение, FixedTimeEquals); (b) публичный sentinel
  QuickLoginOptions.SentinelPassword = '__QUICK_LOGIN_PWLESS__'. Оба
  гейтованы EffectivelyEnabled — вне стенда отбиваются как любой чужой
  пароль.
- SPA (LoginPage.handleQuickLogin) при клике карточки шлёт sentinel в
  поле password. Реальный пароль в JS-бандл больше не попадает; ротация
  секрета больше не требует rebuild'а веба.
- Форму мы НЕ наполняем sentinel'ом — поле password остаётся пустым,
  чтобы ни одно значение не светилось в DOM.

Default QuickLoginOptions.MasterPassword = "" (пустая строка не
матчится в IsMasterPassword). deploy/docker-compose.yml дефолт
QUICK_LOGIN_MASTER_PASSWORD тоже пустой.

Тесты:
- integration (3/3 ok) — добавлены sentinel-кейсы: пропускает активного
  при Enabled=true, отвергается при Enabled=false. Admin12345!-санити-
  логин в Disabled-тесте перенесён вперёд, чтобы снять lockout-счётчик
  от соседей по коллекции (Identity lockoutOnFailure=true).
- e2e stage-quick-login (4/4 ok) — ql02 шлёт sentinel вместо '123456'
  (симулирует реальный SPA flow).

docs/quick-login.md:
- без упоминания реального пароля; хранилище — ~/.fm-watchdog/
  stage-shared-password; ротация-рецепт через python читает файл
  в-процессе (не экспонирует значение в ps/логи).
- раздел «Доступ к стенду» — host-nginx basic-auth для внешнего
  интернета, 192.168.1.x бьёт без неё.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 21:02:36 +05:00
nns 5728b729a8 feat(s30): плашка ТЕСТОВЫЙ СТЕНД + robots noindex (шаг 6/9)
StageBanner — жёлтая полоса сверху всех страниц, когда /api/dev/
quick-login/accounts отвечает 200 (стенд). В проде эндпоинт 404 →
компонент возвращает null, никакая полоса не рисуется.

Решение «stage vs prod» кэшируется в sessionStorage, чтобы не стрелять
запрос на каждой навигации в SPA (один запрос за открытие вкладки).

На стенде динамически добавляется <meta name=robots content=noindex,
nofollow> — не хотим чтобы Google индексировал test.admin.food-market.kz.
Один web-билд ходит в оба окружения; никаких отдельных index.html.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 15:42:49 +05:00
nns 6582333def feat(s30): QuickLoginBlock + интеграция в LoginPage (шаг 5/9)
Над обычной формой логина теперь висит жёлтый блок «Быстрый вход
(тестовый стенд)»: клик по карточке → моментальный логин через
мастер-пароль 123456 (сохранён как константа в LoginPage.tsx — должен
совпадать с server-side QuickLoginOptions.MasterPassword).

Блок сам опрашивает /api/dev/quick-login/accounts: 200 → рендерится,
404 (= прод) → возвращает null, НИЧЕГО не рисует. Так одним и тем же
web-билдом покрываются и стенд, и прод — секрета в UI нет.

Поиск: ?q= на сервере (debounced 200ms), фильтрует по email/ФИО/роли/
магазину. Карточки с badge SuperAdmin / Демо. Lazy-рендер, max-h-80
с overflow-y чтобы не ломать компоновку.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 15:41:50 +05:00
nns 276cba22f8 feat(s29): SuperAdmin UI — бейдж + переключатель расширенного режима (шаг 6/8)
- SuperAdminOrganizationsPage:
  - OrgRow.isAdvancedMode добавлен.
  - Новая колонка «Режим» — бейдж «Простой» (slate) / «Расширенный»
    (violet).
  - Кнопка-экшн (иконка Sparkles) открывает модалку подтверждения
    с разной копией для вкл/выкл; успешный PUT → list.refetch().
  - Выкл-копия напоминает что данные НЕ удаляются и переключение
    обратимо; вкл-копия перечисляет открывающиеся модули.

Переключение из карточки организации — отложено, список-страница
с бейджем и кнопкой закрывает UX.

pnpm run build — ✓ 2.02s, 0 TS errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 11:50:18 +05:00
nns 792ea0fd41 feat(s29): простой режим — упрощённые формы товара/контрагента/приёмки (шаг 5/8)
Везде формула: simpleMode = me && !me.advancedMode && !SuperAdmin.
Значения скрытых полей в state сохраняются и при save на сервер
отправляются как есть; данные НЕ стираются.

- ProductEditPage: скрыты Артикул (auto-gen), Описание, Страна,
  Эталонная цена, Ставка НДС, «В т.ч. НДС», Валюта закупки,
  «Услуга», «Маркируемый», Максимальный остаток, не-retail типы
  цен в блоке «Цены продажи».
- CounterpartiesPage: скрыты Юр.название, Страна, Email, Банк,
  Р.счёт, БИК, Контактное лицо. Остаются: Название, Тип лица,
  БИН/ИИН, Адрес, Телефон, Заметки.
- SupplyEditPage: колонка «Валюта» всегда скрыта в простом режиме
  (одна валюта по умолчанию).

«Поставщик/Покупатель»-галочки отложены — требуют новых колонок
IsSupplier/IsBuyer на Counterparty (новая функциональность).

pnpm run build — ✓ 2.36s, 0 TS errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 11:48:23 +05:00
nns 67c1b590ad feat(s29): web — простой режим скрывает меню / маршруты / виджеты (шаг 4/8)
- AdvancedModeGuard — render-guard: простой режим + не-SuperAdmin → страница
  «Недоступно в простом режиме» с текстом + кнопкой «На главную».
  SuperAdmin пропускается, чтобы можно было проверить работу после
  переключения.
- useMe (+ локальные MeResponse) расширены полем advancedMode: boolean.
- AppLayout.buildNav(roles, advancedMode) — в простом режиме скрыты:
  priceTypes, transfers, demands, promotions, loyaltyPrograms/Cards,
  reportAbc, auditLog, LanguageSwitcher.
- App.tsx — скрытые маршруты обёрнуты в <AdvancedModeGuard>:
  /catalog/price-types, /inventory/transfers*, /sales/demands*,
  /loyalty/programs, /loyalty/cards, /promotions, /audit-log,
  /reports/abc.
- DashboardPage — в простом режиме скрыты виджеты «Топ товаров» и
  «Маржа». Остаются KPI, график продаж, «Последние продажи», «Низкий
  остаток».
- Данные скрытых модулей НЕ удаляются при выключении режима; при
  повторном включении всё видно.

pnpm run build — ✓ 2.6s, 0 TS errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-04 11:43:10 +05:00
nns 6940aa40df feat(s19): bulk-операции + presets + power-user UX (7 пунктов)
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
1. Bulk-обновление товаров — Product.IsArchived + IsAvailableForSale
   (Phase19a миграция), POST /api/catalog/products/bulk-update {ids, op, params}
   с операциями price-adjust (% / абсолют), change-group, archive/unarchive,
   toggle-sale. Одной транзакцией, multi-tenant через query-filter.
   Frontend: checkbox-колонка, sticky bulk-bar, модалка.

2. SavedPresets — domain UserPreset (Phase19b: jsonb ConfigJson,
   unique по OrgId+UserId+PageKey+Name). /api/user/presets CRUD per-user.
   <SavedPresets> компонент с chip-bar и сохранением. Применено к /reports/
   sales/stock/profit + /catalog/products.

3. QuickActionsPalette — Cmd+J открывает отдельную палитру с 14 действиями
   + история topa-10 в localStorage.fm.quickActions.recent. ↑↓/Enter/Esc
   keyboard nav. Cmd+K (поиск) и Cmd+J (действия) — разные палитры.

4. Inline-edit цены — PATCH /api/catalog/products/{id}/price новый endpoint
   с RoundIfNeeded. <InlinePriceCell> с dblclick → input, optimistic update
   + revert при ошибке.

5. CSV import товаров — POST /api/catalog/products/import-csv (rows[]).
   Клиент парсит CSV (auto-detect разделитель ,/;), сервер commit'ит
   транзакцией. autoCreateGroup для новых групп. <ProductsCsvImport>
   модалка с preview и подсветкой ошибочных строк.

6. CSV/XLSX export — endpoint'ы /export на 5 контроллерах (products,
   counterparties, stock, retail-sales, supplies). Reuse существующего
   ReportExport.Csv/Xlsx. <ExportButton> dropdown с двумя форматами.

7. Keyboard-first nav в DataTable — ↑↓/Home/End/Enter/Space/Delete props
   keyboardNav/onSelect/onDelete. Подсветка focused-строки. Документация
   в src/help/keyboard-shortcuts.md + 2 новые HelpTopic'a.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 21:08:48 +05:00
nns 3c731ba532 fix(s18): audit-log employee filter — правильный endpoint и DTO
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
При первом деплое /api/employees вернул 404 — реальный endpoint
/api/organization/employees. Также DTO содержит lastName/firstName/
middleName отдельно, не fullName — собираем строку на клиенте.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 19:01:13 +05:00
nns 9bd4375ae4 feat(s18): TODO cleanup — P0 race fix + helpTooltip + whats-new + contrast + currency + audit filters + notifications
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 Public / Build + push Public (push) Waiting to run
Docker Public / Deploy Public 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
7 пунктов cleanup-спринта:

1. P0: race в GenerateNumberAsync — DocumentNumberRetry helper с
   WithOrgAdvisoryLockAsync (pg_advisory_xact_lock per orgHash/docTypeHash)
   + SaveWithRetryAsync exponential backoff. RetailSalesController POST
   обёрнут в lock. После — 23505 errors 53% → 0 на k6 baseline-replay.

2. HelpTooltip integration — ListPageShell расширен `helpTopic` пропом.
   Применено к 4 страницам (Promotions, Loyalty×2, AuditLog) + inline
   на MoySkladImportPage.

3. WhatsNewBanner — узкий emerald-toast сверху AppLayout. Опрашивает
   /api/whats-new (staleTime=1h), сравнивает buildVersion с
   localStorage.fm.lastSeenBuildVersion. Dismiss сохраняет версию.

4. Color contrast sweep — text-slate-400 в body-text узлах (empty-state,
   table-cells, hints, help) заменён на text-slate-500 dark:text-slate-400.
   19 файлов. Иконки оставлены (decorative, не покрыты axe color-contrast).

5. useFormatCurrency() хук в lib/useFormatCurrency.ts. Берёт
   defaultCurrencySymbol из useOrgSettings + локаль из i18next.
   DashboardWidgets (TopProducts/RecentSales/Margin) переведены — `₸`
   больше не захардкоден.

6. Audit log UI filters — OrgAuditLogPage расширен полями «Кто»
   (Select сотрудников), «Дата с» / «по» (date-input'ы), кнопка
   «Сбросить фильтры». Backend уже умел эти параметры.

7. NotificationCenter — bell-icon в sidebar footer'е с unread badge,
   popover с 30 последних событий (Sale/Supply/LowStock через
   useNotificationsHub). Each item clickable → документ. In-memory.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 18:50:35 +05:00
nns f56c6efab1 feat(s17): onboarding wizard + help kb + feedback + diagnostic + whats-new
Sprint 17 — onboarding-контур: 4-шаг wizard, контекстный help, in-app
feedback, admin self-diagnostic, /whats-new из CHANGELOG.md.

Ключевые цифры:
- Wizard: 4 шага + skip каждого, 7 e2e тестов ✓ за 20 секунд.
- Diagnostic: 7 параллельных проверок, ~80ms на stage.
- Bundle impact: initial +4 KB gzip (только FeedbackWidget +
  HelpTooltip + EmptyStateWithDemo в основном bundle; страницы lazy).
- Regression-suite: 35 → 42 flows + 60 → 66 visual snapshots.

Backend (новые endpoint'ы):
- /api/admin/diagnostic/run — 7 параллельных проверок (DB, SMTP,
  MinIO, Hangfire, диск, сертификаты, бэкап). Task.WhenAll, ~80ms.
- /api/feedback — POST {category, message}, email на FromEmail +
  Telegram (если SupportTelegram:* настроены). Rate-limit 5/час.
- /api/whats-new — парсер CHANGELOG.md, возвращает {buildVersion,
  items}. Dockerfile.api копирует CHANGELOG.md в content-root +
  пишет VERSION из GIT_SHA build-arg.

Frontend:
- /onboarding-wizard — 4-step builder, состояние в useState,
  localStorage.fm.wizardCompleted после завершения.
- <HelpTooltip topic="key"/> — popover на каждой странице, mapping
  src/lib/help-topics.ts (13 keys).
- /help — knowledge base, 7 markdown topics через import.meta.glob,
  mini-renderer без heavy deps, fuzzy search.
- /whats-new — список из /api/whats-new, иконки по типу (feat/fix).
- /admin/diagnostic — Admin/SuperAdmin only, 🟢/🟡/🔴 индикаторы.
- <FeedbackWidget> в sidebar footer + ссылки на /help и /whats-new.
- <EmptyStateWithDemo> placeholder для будущих видео-демо.

scripts/generate-changelog.sh — git log feat:/fix: за 90 дней
→ CHANGELOG.md (307 строк сгенерировано).

Wizard UX-screenshots в docs/sprint17-screenshots/ (6 PNG: 4 шага +
help + diagnostic).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 17:04:26 +05:00
nns 9588d03bf4 test(s15): axe a11y + focus traps + unit coverage 80% + property tests + backup drill
Sprint 15 финальный — реальные axe + coverage + pg_restore numbers.

Ключевые цифры:
- axe-core: critical=0 on 10 страниц stage'а; serious 12→9
  после фиксов (sidebar contrast + 8 icon-only back-arrow aria-labels).
- Unit coverage: Application 56%→83%, Domain 11%→79%, combined
  60%→80%. Тестов 68→147 (+79).
- Backup recovery drill: RTO ~25 секунд end-to-end
  (pg_dump 2s + pg_restore 4s + dotnet startup 19s).

Что сделано:
1. @axe-core/playwright + stage-ui-15 (10 страниц) + stage-ui-16
   (SR smoke на login: getByLabel, role=alert, aria-describedby,
   keyboard nav).
2. useFocusTrap hook (WCAG 2.4.3 + 2.1.2): return-focus, mount-focus,
   Tab cycle. Подключён к Modal + ConfirmDialog с opt-in
   defaultFocus='cancel'|'confirm'. ConfirmDialog по дефолту фокусит
   Cancel для destructive actions (safer чем Enter→Delete).
3. A11y фиксы:
   • text-slate-400→text-slate-500 в sidebar (contrast 2.63→4.61).
   • 8 страниц edit с back-arrow Link — aria-label + aria-hidden
     на иконке + текст-slate-500 цвет.
   • Modal close button — то же.
   • LoginPage — aria-invalid/aria-describedby/role=alert на
     ошибках валидации.
   • Field component — role="alert" на error span (announce'ит SR).
4. 8 файлов unit-тестов: PhoneNormalization, PagedRequest,
   RequiredGuid, RolePermissions (Domain), DomainPocoSmoke,
   DomainFullPropertyTouch, CatalogDtosSmoke, StockServiceProperty
   (4 seeds × 4 size + batch + 2-product isolation).
5. Backup-drill: pg_dump со stage'а → fresh postgres:16-alpine →
   pg_restore → dotnet run против восстановленной БД → /health/ready
   Healthy. Команды и timing в RUNBOOK.md.
6. Docs review:
   • MULTI-TENANCY чеклист «добавить tenant-сущность» расширен с 6
     до 19 шагов (Domain → EF Config → Migration с Xmin →
     RolePermissions → Validation → Controller + RequiresPermission →
     Audit + SensitiveOpsAudit → property tests).
   • ARCHITECTURE.md — Sprint 13-15 changes таблица.
   • DEVELOPER-GUIDE.md — «что добавилось после первого guide'а» +
     a11y pitfalls в «что НЕ делать».

Stage smoke ✓. Это финальный автономно-безопасный спринт. Дальше
нужен вход от user'а (ОФД keys, MoySklad tokens, Windows для POS,
прод-деплой план, kz-перевод, реальный SMTP).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 14:53:38 +05:00
nns e13dd6937f perf(s14): индексы + N+1 fix + bundle -50% + WebP variants + pool + Hangfire timing
Sprint 14 — производительность с реальными замерами до/после.

Ключевые цифры:
- Sales-report SQL: 9.53ms → 7.09ms mean (-25%) после N+1 fix +
  индексов.
- Initial JS bundle: 1456 KB → 706 KB raw (-51%); gzip 389 KB →
  196 KB (-50%) через React.lazy на 30 редких страниц + Recharts.
- Lighthouse /login: Perf 89, A11y 92, BP 100 (target ≥85/90/90 ✓).

Подробности по каждому пункту + методология замеров — в
docs/sprint14-progress.md.

Что сделано:
1. Phase14a_PerfIndexes — composite (Org,Status,Date), partial
   (WHERE Status=1 AND NOT IsReturn) + INCLUDE, и composite
   stock_movements(Org,OccurredAt).
2. SalesReportController.FetchAsync — раньше каждая строка
   результата делала CASE WHEN ELSE (SELECT ... LIMIT 1)
   correlated subquery на RetailPoint.Name и User.FullName.
   Заменено на 2 IN-batch'a + dictionary lookup в C#.
3. App.tsx React.lazy для отчётов, audit-log, loyalty, super-admin,
   settings, all rare edit pages. Recharts вынесен в lazy chunk
   Dashboard'а (KPI рендерятся сразу).
4. SixLabors.ImageSharp v3.1.6 + ImageVariantService — генерирует
   thumb 256/medium 800 WebP@80 при загрузке. UploadsController
   ?size=thumb|medium с fallback. React <ProductImage> — <picture>
   + srcset.
5. ApplyDefaultPoolConfig на старте: Max=100, Min=10 (грей пул),
   Idle=300, Max Auto Prepare=20.
6. Lighthouse на /login /forgot-password /reset-password —
   все три проходят пороги.
7. JobTimingFilter + HangfireGlobalFilterRegistrar — каждый
   recurring job логирует длительность; >30s = Warning.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 13:21:39 +05:00
nns 0d3ef81f72 feat(s11): ОФД-scaffolding — IFiscalProvider + 4 провайдера + UI/тесты
Sprint 11 — каркас для интеграции с операторами фискальных данных РК.
Реальные ApiKey'и появятся у user'а позже; задача — построить такой
фрейм, чтобы подключение оператора сводилось к вписыванию кредов в UI
без правок кода/деплоя.

Что сделано:
- IFiscalProvider (Application/Common/Fiscal) + FiscalResult,
  FiscalProviderKind (None/Mock/Webkassa/Kassa24/OfdSolo),
  IFiscalProviderFactory, FiscalNotConfiguredException.
- 4 реализации в Infrastructure/Fiscal:
  • MockFiscalProvider — фейк MOCK-<8hex> через 300мс, идемпотентный
    по Sale.Id (используется dev/stage и интеграционными тестами);
  • WebkassaProvider — полный HTTP-pipeline Authorize→Check, парсинг
    JSON-ответа, NDS-в-ставке, retry-safe через ExternalCheckNumber;
  • Kassa24Provider / OfdSoloProvider — скелет с тем же контрактом,
    RegisterAsync бросает FiscalNotConfiguredException (нужны
    спецификации API от user'а, NDA-only).
- Миграция Phase11a: 5 колонок в retail_sales (FiscalNumber, QrCode,
  Url, ProviderTxId, ProviderKind) + 5 в organizations (FiscalProvider
  NOT NULL default 0, ApiKey/Secret encrypted, CashboxUniqueNumber,
  ApiBaseUrl). Default 0 = обратная совместимость, существующие чеки
  и продажи без фискализации работают как раньше.
- RetailSalesController.Post — TryFiscalizeAsync после commit'а
  stock-транзакции. Best-effort: сетевые/HTTP-ошибки логируются, чек
  остаётся проведённым. Идемпотентность по IsNullOrEmpty(FiscalNumber).
- OrgFiscalSettingsController: GET/PUT настройки + GET /providers
  (опции для select'а) + POST /test-send (фейк-чек к выбранному
  провайдеру, не сохраняет в БД).
- UI: FiscalSection в OrganizationSettingsPage с password-input'ами
  для ApiKey/Secret (шифруются DataProtection.purpose=foodmarket.fiscal,
  в GET — только has-* флаги), спец-значение "__clear__" для снятия,
  кнопка «Тестовая отправка».
- Тесты: 11 unit (Mock 5 + Webkassa payload 6) + 3 integration
  (Mock сохраняет FiscalNumber, test-send даёт MOCK-номер, None
  не фискализует).
- docs/ofd-integration.md — гид с архитектурой, шагами подключения
  Webkassa (полный pap), TODO для Касса24/ОФД-Соло, безопасностью
  кредов, retry-сценариями.

Все 68 unit + 8 integration в Fiscal/Loyalty/RetailOversell — зелёные.
Web vite build — зелёный.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 02:27:17 +05:00
nns 786dacb081 feat(s10-4): dark mode полировка + Cmd+K палитра + аудит-spec
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
Docker Web / Build + push Web (push) Has been cancelled
Docker Web / Deploy Web on stage (push) Has been cancelled
S10-4: script-патчер обработал 29 файлов (pages + components).

Подход: посимвольный скан каждой строки с className. Если есть
text-slate-{500..900} / bg-white / bg-slate-{50,100} / border-slate-{100,200,300}
БЕЗ dark:* для того же префикса (text/bg/border/divide/hover-bg) — добавляем
соответствующий dark-companion рядом. Идемпотентен.

Стратегия маппинга:
- text-slate-500 → +dark:text-slate-400
- text-slate-700 → +dark:text-slate-200
- text-slate-900 → +dark:text-slate-100
- bg-white → +dark:bg-slate-900
- bg-slate-50 → +dark:bg-slate-800/60
- border-slate-200 → +dark:border-slate-800
- hover:bg-slate-50 → +dark:hover:bg-slate-800/50
- … и аналогичные.

Skip если на той же строке уже есть dark:<prefix>-* (например
dark:bg-blue-500) — не трогаем чужие осознанные dark-выборы.

stage-ui-s10-dark-audit.spec.ts снимает 20 скриншотов (10 страниц
× light/dark) в reports/dark-mode/. Визуально проверены Dashboard,
ABC-report, Products — контраст ок, brand-зелёный сохранён,
sidebar/таблицы/виджеты читаемы.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 01:30:41 +05:00
nns f9fa028fe5 feat(s10-3): глобальная Cmd+K палитра + GET /api/search/global
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
S10-3: командная палитра для быстрой навигации и поиска.

Backend GlobalSearchController:
- GET /api/search/global?q=… ищет в 3 источниках: товары (name/article/
  barcode startsWith), контрагенты (name/bin contains), документы
  (Supply.Number, RetailSale.Number, Demand.Number) — по ≤5 в каждой
  группе. Tenant-scoped, требует ≥2 символа в q.
- Lower-cased contains; EF8 OrderBy на record-projection ломается,
  поэтому проектируем в anonymous, потом маппим в DocumentHit.

Frontend CommandPalette.tsx:
- Глобальный хоткей Cmd+K / Ctrl+K (listener на document в AppLayout).
- Статический список 20 страниц для навигации без меню (даже без API).
- Дебаунс query 200мс → GET /api/search/global при q ≥ 2 символов.
- Recent items: localStorage 'fm.cmdk.recent', последние 10 выбранных
  показываются когда q пустой.
- Подсветка совпадений через RegExp split + <mark>.
- Хоткеи: ↑↓ Enter Esc; группированный список (Recent / Pages /
  Товары / Контрагенты / Документы).

Проверено на стэйдже: q='колбас' → 3 продукта, q='Алматы' →
2 контрагента (поставщики), q='ПР-Y1-00019' → 5 retail-sale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 01:20:05 +05:00
nns 1044818fbb feat(s10): year-demo seeder + 4 dashboard виджета + week-stats
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
S10-1: YearDemoSeeder — POST /api/admin/seed-demo?years=1.
- 8 групп × 25 товаров = 200, 30 контрагентов, 80 приёмок равномерно
  по году, 1500 розничных продаж с месячной сезонностью (Dec пик ×1.6,
  Jul-Aug спад ×0.7), 20 customer-returns, 8 demands, 10 losses, 3
  transfers, 5 inventories.
- Маркер артикулов Y1- (параллельно с DEMO-короткий сидер). Гард на
  существующую активность чтобы не лить хаос поверх ручной работы.
- Bulk StockMovement + переагрегация Stocks в конце транзакции —
  16.5s на dev-vm vs 60+s если бы per-document SaveChanges.

S10-2: DashboardController + 4 виджета:
- GET /api/dashboard/top-products?days&limit — top-N по gross-выручке
  (без net-вычета returns; для точного есть /api/reports/sales).
- GET /api/dashboard/low-stock?limit — Stock.Quantity ≤ Product.MinStock.
- GET /api/dashboard/recent-sales?limit — последние N посt'ed чеков.
- GET /api/dashboard/margin?days — Σ(LineTotal) - Σ(qty × Product.Cost),
  marginPercent к выручке.
- /api/sales/retail/stats расширен revenueThisWeek + transactionsThisWeek.
- Frontend: components/DashboardWidgets.tsx с 4 виджетами через
  React.lazy + Suspense. SignalR SalePosted инвалидирует все 4.
- KPI блок: today / week / month + avg-ticket (4 плитки, prev-month
  стал delta на month-плитке).

Проверено на стэйдже год-демо: top-5 за 365 дн. — «Колбаса сервелат
300г» 286440 ₸ / 32 транзакции. Margin 40% за 30 дн.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 01:03:36 +05:00
nns 12d833f035 fix(pwa): bump cache version + filter SignalR-race errors in PWA test
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
Docker Web / Build + push Web (push) Has been cancelled
Docker Web / Deploy Web on stage (push) Has been cancelled
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:33:02 +05:00
nns 6f9dd11b0a fix(pwa): SW не вмешивается в /hubs/* — SignalR negotiate сломался
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
SignalR через web sockets/long-poll стримит данные, его нельзя кешировать.
В пред-версии SW не интерсептировал POST, но GET-fetch для negotiate
проходил через SW pipeline и валился TypeError'ом.

Фикс: явный return на /hubs/* перед всеми стратегиями.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:28:06 +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 dc68c997c9 fix(loyalty): убрать unused imports (TS6133)
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
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:10:44 +05:00