180 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c7bf7e13ce |
content(public): нейтральный тон, без упоминаний сторонних систем
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 58s
CI / Web (React + Vite) (push) Successful in 40s
Docker Public / Build + push Public (push) Successful in 28s
Docker Public / Deploy Public on stage (push) Successful in 11s
— Все материалы (главная, /pos, /about, FAQ, kb, blog) переведены на нейтральные формулировки: «другие системы учёта», без имён. — Новая страница /import — единая точка входа по миграции каталога; описывает Excel/CSV, REST API и выгрузку 1С. — Удалены публичные kb/blog-статьи, целиком построенные вокруг миграции с конкретного продукта. — /migration-from-moysklad убран из sitemap; nginx делает 301 на /import. — blog schema расширена optional-полями (author, category, cover_image), чтобы новый frontmatter валидировался content collection. — Грамматические правки: «Импорт из других систем». Финальный grep по пакету по списку имён конкурирующих SaaS-учётных продуктов — пусто. Smoke по 9 ключевым URL — 200; старый URL — 301. |
||
|
|
e7899b4185 |
content(public): naполнить блог + KB + about/contacts; убрать упоминания конкурентов
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m4s
CI / Web (React + Vite) (push) Successful in 40s
Docker Public / Build + push Public (push) Successful in 25s
Docker Public / Deploy Public on stage (push) Successful in 10s
Phase 6 контентная часть — частично: Блог (3 поста из /tmp/content/blog-and-kb.md, content collection): - launch — запуск Food Market - import-moysklad — пошаговый гид миграции (внутренняя инструкция, frontmatter) - cash-with-scales — почему касса с весами Масса-К из коробки База знаний (5 статей, content collection с category + order): - quickstart, import-moysklad, pos-setup, billing, faq Страницы /blog и /kb перерисованы — рендерят список из коллекций с группировкой по категориям, отдельные [slug].astro template'ы рендерят markdown с типографикой prose-md. /about и /contacts наполнены реальным контентом из /tmp/content/about-contacts-pricing-features.md (без заглушек). Контакты — 4 канала (email/phone/чат/реквизиты) с placeholder-ами для будущих контактов после регистрации юр.лица. Очистка от упоминаний конкурентов (по правилу: не сравнивать с конкретными системами и не выводить чужие цены публично): - Hero/FAQ на главной — заменено «Импорт из МойСклад» на «импорт каталога одной кнопкой», убран FAQ-вопрос «Чем отличаетесь от...», заменён на общий «Чем хорош Food Market?». - Footer: пункт «Импорт из МойСклад» → «Импорт каталога». - /migration-from-moysklad удалён (содержал сравнительную таблицу с ценами МойСклад). Возможен возврат как KB-инструкция позже. - features/pos/pricing/integrations/changelog/for-grocery/for-pharmacy — sed-замена «МойСклад» → «другие системы», ссылки на /migration-from-moysklad → /features. - В content-collections блог/KB остались упоминания (юзер пришлёт PATCH-версии текстов отдельно). Контейнер пересобран и задеплоен на стенд: 30 страниц, smoke на /, /blog, /kb, /about, /blog/launch — все 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
55191e089d |
feat(deploy): Phase 6 — публичный сайт на food-market.zat.kz, админка на app.
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 45s
CI / Web (React + Vite) (push) Successful in 36s
Docker API / Build + push API (push) Successful in 50s
Docker Public / Build + push Public (push) Successful in 45s
Docker Web / Build + push Web (push) Successful in 6s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Public / Deploy Public on stage (push) Successful in 11s
Docker Web / Deploy Web on stage (push) Successful in 11s
Доменная схема (по решению юзера): food-market.zat.kz → новый Astro public-сайт (порт 8082, контейнер food-market-public) app.food-market.zat.kz → существующая админка (food-market.web, порт 8081) API остаётся на app.* под /api/*. Изменения: - docker-compose: добавлен сервис public (image food-market-public:latest, 127.0.0.1:8082:80). На стенде .env дополнен PUBLIC_TAG=latest, контейнер поднят, smoke на / и /pricing проходит. - Forgejo workflow .forgejo/workflows/docker-public.yml — отдельный билд при изменениях в src/food-market.public/**: docker build с --build-arg PUBLIC_SITE_URL=https://food-market.zat.kz и --build-arg PUBLIC_APP_URL=https://app.food-market.zat.kz, push в локальный registry, deploy через docker compose pull+up. TG-пинг. - Nginx (на стенде вручную, не через репо): - Новый блок food-market-app.conf для app.food-market.zat.kz — проксирует на :8081 (web), вместе с /api/admin/import/ и /tg-webhook путями. Certbot --nginx выпустил SSL. - Старый food-market-stage.conf переписан на public — проксирует на :8082, использует существующий SSL для food-market.zat.kz. - API CORS: добавлены food-market.zat.kz, app.food-market.zat.kz, food-market.kz, app.food-market.kz в AllowedOrigins (publicу нужен food-market.zat.kz для signup-запросов, админке нужен app.*). - JWT cookie domain не настраиваем — проект использует localStorage, cross-domain auth-bridge через URL fragment (см. AuthBridgePage), что безопаснее cookie с .food-market.zat.kz. - Хардкодов food-market.zat.kz в food-market.web/src не нашлось — всё через относительные URL. Существующие админ-сессии: токены в localStorage привязаны к food-market.zat.kz origin. После переезда юзеры увидят на этом домене публичный сайт без своих токенов — нужно перелогиниться на app.food-market.zat.kz. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a4cbb06bb3 |
feat(public): Phase 6 — публичный маркетинговый сайт food-market.public на Astro
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 48s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 50s
Docker Web / Build + push Web (push) Successful in 40s
Docker API / Deploy API on stage (push) Successful in 18s
Docker Web / Deploy Web on stage (push) Successful in 11s
Новый пакет src/food-market.public/ — отдельный фронт для маркетинга и
самостоятельной регистрации магазинов-клиентов в SaaS Food Market.
Существующая админка food-market.web НЕ затронута.
Стек: Astro 4 + React 19 islands + Tailwind v3 (палитра идентична
food-market.web — единый бренд), TypeScript 6, content collections для
юр.документов. Static-сайт через nginx, gzip + immutable cache на assets.
Карта страниц (23):
- / — главная (Hero + 3 выгоды + скриншот +
6 вертикалей + 6 модулей + Касса +
Интеграции + 3 тарифа + соцпруф +
FAQ + финальный CTA)
- /features — модули по сценариям
- /pricing — тарифы + интерактивный конструктор
«Бизнес» (per-unit: 10000 база +
2000/магазин + 500/касса + 500/склад,
слайдеры, передача params в /signup)
- /pos — УТП лендинг кассы для Windows + весов
- /migration-from-moysklad — УТП лендинг миграции с МойСклад
(сравнительная таблица + 3 шага)
- /integrations — список интеграций
- /for-grocery|pharmacy|cafe|alcohol|clothing|household — 6 вертикалей
с уникальными фишками (весовой,
серии/сроки, модификаторы и комбо,
акцизные марки, размерные сетки,
гарантийные сроки)
- /signup — регистрация (React-island форма)
- /about /contacts /kb /blog /status /changelog — компания + ресурсы
- /legal/{offer,privacy,consent,requisites} — реальные юр.документы
из /tmp/legal/ как Astro content
collection (markdown с frontmatter,
динамический [slug].astro template,
720px max-width, line-height 1.7,
prose-legal стили)
- /sitemap.xml — ручной генератор (sitemap-плагин
конфликтует с Astro 4.16, заменён
на простой APIRoute)
React-острова (3):
- BusinessTariffBuilder — слайдеры + расчёт total + ссылка на signup
- SignupForm — email/password/orgName/phone/plan + валидация + agree
- FAQ — accordion 7 вопросов
API: новый POST /api/auth/signup создаёт Organization + AppUser
(Identity Admin role) + Owner Employee + полный bootstrap через
DevDataSeeder.SeedTenantReferencesAsync (units, price-types, store,
cassa, 6 ролей). Токены НЕ выпускает — фронт сразу делает обычный
/connect/token (password grant) и получает access/refresh без
дублирования OpenIddict-логики. На signup-форме — auth-bridge:
токены передаются через URL fragment в админку
APP_URL/auth-bridge#access=...&refresh=...&welcome=1, AuthBridgePage
кладёт в localStorage и редиректит на /?welcome=signup.
URL-домены через env-переменные (юзер ещё выбирает финальный):
- PUBLIC_SITE_URL — canonical/OG/sitemap (default https://food-market.kz)
- PUBLIC_APP_URL — admin/API endpoint (default https://food-market.zat.kz)
Nginx-конфиг для деплоя сайта — заготовка-template в
deploy/nginx/food-market-public.conf.template, не применён —
ждёт решения по домену.
Dockerfile multi-stage (node:20-alpine build → nginx:1.27 runtime),
build-args PUBLIC_SITE_URL/PUBLIC_APP_URL, deploy/nginx.conf gzip +
immutable cache + try_files для pretty URLs.
SEO: OG-теги, twitter-card, canonical, JSON-LD SoftwareApplication
схема, robots.txt → sitemap-index, lang=ru-KZ.
Admin-side: /auth-bridge route в food-market.web — принимает токены
из URL fragment, кладёт в localStorage (fm.access_token / fm.refresh_token),
редиректит на /. Fragment чтобы access_token не попадал в Referer.
23 страницы билдятся без ошибок. Контейнер собирается. Деплой на
конкретный домен — отдельным шагом после решения юзера.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
3e25498c3b |
feat(super-admin): настраиваемый retention period для архивных орг
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 48s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 47s
Docker Web / Build + push Web (push) Successful in 32s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Раньше «удалить орг навсегда» было захардкожено на 30 дней архива. Теперь — глобальная системная настройка SuperAdmin'а. Domain/DB: - SystemSettings : Entity (single-row table system_settings). Поле ArchiveRetentionDays (int, default 30). Структура расширяется именованными полями по мере необходимости — без key-value generic'а. - Migration Phase4e_SystemSettings создаёт таблицу с default 30. - DevDataSeeder: при первом старте создаёт single-row дефолт. API: - GET /api/super-admin/settings — текущие настройки. - PUT /api/super-admin/settings — обновить с валидацией [0..3650]. Audit-log запись ActionType=EditSystemSettings с before/after. - SuperAdminOrganizationsController.Delete: хардкод 30 заменён чтением SystemSettings.ArchiveRetentionDays. При retention=0 — удаление доступно сразу после архивации. UI: - /super-admin/settings — страница «Системные настройки». Select из 6 опций (0/1/3/7/14/30), warning-баннер при выборе «Немедленно». Кнопка «Сохранить» disabled пока нет изменений. - В SuperAdminLayout убрана пометка «скоро» с пункта «Системные настройки» — раздел активен. - SuperAdminOrganizationsPage: кнопка «Удалить навсегда» теперь читает retentionDays из API; tooltip показывает оставшиеся дни «Доступно через X дн. (retention N)»; при retention=0 — всегда active для архивных орг. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4152eb1291 |
fix(super-admin): убрать цикл редиректа, регресс override после пакета задач
Some checks are pending
КОРНЕВАЯ ПРИЧИНА: SuperAdminLayout имел useEffect
if (getOrgOverride()) navigate('/dashboard', { replace: true })
который автоматически выкидывал в tenant-режим при любом заходе на
/super-admin/* если в localStorage остался override (например с прошлой
сессии или после нечистого выхода). Это создавало цикл: SuperAdmin
кликает «Системная консоль» в меню → попадает на /super-admin/... →
useEffect видит override → navigate /dashboard → tenant TenantRouteGuard
проверяет: override есть → пропускает, но юзер видит tenant с баннером
вместо системной консоли.
Если override содержал «зависший» orgId с прошлой сессии и юзер ожидал
вернуться в SuperAdmin консоль — он попадал в tenant-режим, а попытки
вернуться через ссылки sidebar лечились этим же useEffect снова в /
dashboard. Юзер видел тост от TenantRouteGuard в редких случаях когда
localStorage не успевал прочитаться (race с rAF).
Фикс:
- Удалён useEffect (+ unused useNavigate импорт). SuperAdmin может
свободно перемещаться между системной консолью и tenant-режимом
при активном override; снимает override только баннером «Выйти из
режима» либо переключает другую орг через picker.
- TenantRouteGuard: повторная проверка getOrgOverride() через
requestAnimationFrame (защита от любых race с localStorage), и
разовый guard-флаг (useRef) чтобы не редиректить дважды на одной
странице. Console.warn перед редиректом — облегчает диагностику
если регресс повторится.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
18a0370d96 |
fix(migration): Phase4d таблица называется units_of_measure, не units
Some checks are pending
Опечатка: в Up() миграции стояло table:\"units\" — реальное имя в БД units_of_measure (по EF snapshot). API падал при старте на стенде: «relation public.units does not exist», 502 на login. На стенде SQL ALTER COLUMN накачен вручную + запись в __EFMigrationsHistory; миграция fix только для будущих чистых баз. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
58038c9cf7 |
feat(directories): двухуровневые справочники Группы и Ед.измерения (системные + tenant)
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 45s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 46s
Docker Web / Build + push Web (push) Successful in 30s
Docker API / Deploy API on stage (push) Failing after 36s
Docker Web / Deploy Web on stage (push) Successful in 11s
Концепция: ProductGroup и UnitOfMeasure становятся двухуровневыми
справочниками. Системные эталонные записи (OrganizationId=NULL,
управляются SuperAdmin'ом) видны всем tenant'ам как «Эталон»
и read-only. Tenant'овские (OrganizationId=<orgId>) — обычная изоляция,
полный CRUD у админа орги.
Архитектура:
- IOptionalTenantEntity { Guid? OrganizationId } — новый интерфейс
в Domain/Common. ProductGroup и UnitOfMeasure отнаследованы от
Entity и реализуют его.
- AppDbContext.ApplyOptionalTenantFilter<T>: query-filter для
IOptionalTenantEntity пропускает запись с OrganizationId=NULL для
всех tenant'ов + tenant'овские по выбранной orgId. SuperAdmin без
override видит всё, в override — только NULL+своё.
- StampTenant: при Add для IOptionalTenantEntity — null оставляется
если SuperAdmin без override (системная), иначе подставляется
tenant.OrganizationId.
- Миграция Phase4d_OptionalTenantOnDirectories: ALTER COLUMN
OrganizationId DROP NOT NULL на product_groups и units_of_measure.
Existing данные FOOD MARKET (11 групп, 5 единиц) сохраняются как
tenant'овские — additive change, ничего не теряется.
- DTO: UnitOfMeasureDto и ProductGroupDto получили nullable
OrganizationId; фронт читает его для показа badge «Эталон».
- Защита мутаций: PUT/DELETE контроллеры теперь возвращают Forbid()
если запись OrganizationId=null и юзер не SuperAdmin (только
суперадмин может править/удалять системные).
Frontend:
- Badge «Эталон» (indigo) рядом с именем системной записи в обеих
страницах.
- Клик по строке системной записи → alert «Изменения недоступны…».
- SuperAdmin sidebar: новые пункты «Группы (эталон)» (FolderTree)
и «Ед. измерения (эталон)» (Ruler) под «Справочники». Страницы
реиспользуют существующие компоненты — для SuperAdmin без override
фильтр возвращает все записи, что в Phase 4+ можно ужесточить
отдельным эндпоинтом «только системные» (?orgId=null).
Decision (нонстоп-выбор по ТЗ): nullable OrganizationId через
IOptionalTenantEntity, не sentinel Guid.Empty — чище, безопаснее,
ясная семантика. Существующие группы FOOD MARKET НЕ мигрированы в
системные (как просил юзер) — пусть SuperAdmin сам создаст эталоны.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8466cf928c |
feat(roles): системные роли read-only + русские имена + чистка дубликата у admin
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 40s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 45s
Docker Web / Build + push Web (push) Successful in 34s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Концепция: «Супер администратор» — платформенная Identity-роль SuperAdmin. «Администратор» — организационная роль внутри Employee (IsSystem=true в EmployeeRole). Они НЕ должны дублироваться у одного юзера. - Сидер: admin@food-market.local получает только Identity-роль SuperAdmin. Догоняющая ветка для существующих стендов: если есть Identity-роль Admin — RemoveFromRoleAsync. На стенде AspNetUserRoles почищен SQL'ом. - AppLayout: translateRoles() переводит SuperAdmin → «Супер администратор», скрывает Identity-роль Admin (org-уровень показывается через Employee/Role, не через Identity). - EmployeeRolesPage: клик по строке системной роли → alert «Системная роль, изменения недоступны». Edit-модалка для системных была частично defensive (disabled чекбоксы Phase 2c), теперь точка входа закрыта целиком. Кастомные роли — без изменений. EmployeeRole.IsSystem поле уже было — миграция не нужна. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6395cf348d |
feat(super-admin): перенести справочник Стран в системную консоль
Some checks failed
CI / POS (WPF, Windows) (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
CI / Backend (.NET 8) (push) Successful in 49s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Has been cancelled
Country — глобальный справочник (Entity, не TenantEntity), магазины- клиенты выбирают страны из готового списка но не управляют ими. Управление переносится в SuperAdmin консоль. Изменения: - API: POST/PUT /api/catalog/countries теперь Authorize(Roles=SuperAdmin) (раньше был SuperAdmin,Admin). DELETE и так был SuperAdmin. - GET остаётся [Authorize] без роли — нужен tenant'у для селектов в формах создания орги/контрагентов/товаров. - Tenant AppLayout: убран блок «Справочники» с пунктом «Страны». Иконка Globe больше не импортируется в tenant-меню. - Tenant роут /catalog/countries удалён из App.tsx. - В OrganizationSettingsPage ссылка «откройте справочник Страны» заменена на текст «обратитесь к администратору платформы». - SuperAdminLayout: новый блок «Справочники» с пунктом «Страны» (/super-admin/countries). Иконка Globe. - Роут /super-admin/countries использует существующий CountriesPage — компонент unchanged, страница теперь рендерится в SuperAdminLayout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2b9623d5cc |
fix(tenancy): SuperAdmin override должен применять tenant filter выбранной орги
Some checks are pending
🔴 КРИТИЧНЫЙ БАГ ИЗОЛЯЦИИ. SuperAdmin в режиме «открыть как Demo Market»
видел товары FOOD MARKET (29540 чужих записей вместо 0 своих).
Корень проблемы — query-filter в AppDbContext:
e => _tenant.IsSuperAdmin || e.OrganizationId == _tenant.OrganizationId
IsSuperAdmin → весь предикат становится true → все записи всех орг.
В режиме override OrganizationId уже корректно подменялся на
выбранную орг, НО bypass через IsSuperAdmin делал подмену
бессмысленной — фильтр всё равно пропускал всё.
Фикс — добавил IsTenantOverride флаг в ITenantContext и переписал:
e => (_tenant.IsSuperAdmin && !_tenant.IsTenantOverride)
|| e.OrganizationId == _tenant.OrganizationId
То есть SuperAdmin обходит фильтр ТОЛЬКО когда не в override. В
override-режиме он работает в контексте выбранной орги как обычный
юзер — фильтр применяется.
HttpContextTenantContext.IsTenantOverride возвращает true когда
текущий запрос — HTTP, юзер в роли SuperAdmin и присутствует header
X-Org-Override с валидным GUID. AsyncLocal-override (background-задачи
импорта/Hangfire) намеренно НЕ считается tenant-override — там
IsSuper=false по умолчанию и фильтр и так применяется.
Smoke-test ДО фикса (воспроизведение):
GET /products X-Org-Override=DemoId → total 29540 (баг: чужие)
GET /products X-Org-Override=FoodId → total 29540
GET /products без header → total 29540 (legit super)
После деплоя ожидается:
GET /products X-Org-Override=DemoId → 0 (Demo Market пуст)
GET /products X-Org-Override=FoodId → 29540 (своих)
GET /products без header → 29540 (legit super bypass)
Затронуты ВСЕ tenant-сущности (фильтр применяется через reflection
ко всем ITenantEntity): products, counterparties, supplies, stocks,
movements, retail-sales и т.д.
DesignTimeTenantContext получил IsTenantOverride=false (он только для
EF tooling).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7a21c83d3e |
ui(super-admin): SaaS-метрики на главной системной консоли (placeholders)
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 47s
CI / Web (React + Vite) (push) Successful in 40s
Docker API / Build + push API (push) Successful in 45s
Docker Web / Build + push Web (push) Successful in 30s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Food Market — SaaS для розницы, SuperAdmin это владелец платформы, а не сотрудник магазина. Операционные метрики магазинов («Товаров 29540», «Приёмок за месяц») для него бесполезны — это для tenant dashboard'а конкретной орги. KPI блок на /super-admin переработан под кабинет SaaS-провайдера: Top row (4 карточки): - Организаций (как было — клиентская база) - Платящих клиентов — placeholder, accent emerald, muted - MRR (₸ / мес) — placeholder, accent violet, muted - Должники — placeholder, accent rose, muted Second row (2 карточки): - Пользователей (всего/активных) - Регистраций за 30 дней — реальное значение (COUNT Organizations WHERE CreatedAt >= now-30d) Заглушки получили проп muted=true: фон чуть серее (slate-50/60), значение «—» более бледным slate-400, иконка остаётся полноцветной чтобы было видно «здесь будут данные после Phase 4». В hint — «Скоро · после внедрения биллинга». API: DashboardStats потерял TotalProducts/TotalSuppliesThisMonth, получил RegistrationsLast30Days. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4dafdc8995 |
feat(super-admin): рабочий quick-switch + UI-блокировка мутаций в read-only
Some checks are pending
БАГ: dropdown «Открыть организацию» в topbar системной консоли вёл
сначала на reload текущего /super-admin, а оттуда внутренний редирект
конкурировал с TenantRouteGuard'ом, и юзер видел alert «Откройте
через "Открыть как…"». Фикс — setOrgOverride получил опциональный
{ redirectTo }, делает window.location.assign(target) вместо reload.
Точки вызова обновлены:
- dropdown в SuperAdminLayout topbar → redirectTo='/dashboard'
- кнопка «Открыть как» в строке таблицы орг → redirectTo='/dashboard'
- кнопка «Выйти из режима» в баннере → redirectTo='/super-admin/organizations'
Хук useReadOnly() централизует «override активен И edit-mode не
включён» в один { readOnly, reason }. Button по умолчанию считает
variant='primary' и variant='danger' мутирующими (опт-аут через
mutating={false} для редких primary-без-мутаций) — в read-only они
автоматически disabled с tooltip «Только просмотр. Включите
редактирование в баннере…». Variant='secondary' и 'ghost' не
блокируются — Cancel/Назад/Закрыть остаются кликабельны. Серверный
403 (ReadonlyOverrideMiddleware) остаётся как safety-net.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9d89a2aeee |
ui(super-admin): hero-блок и KPI карточки на главной + три полезных секции
/super-admin переработан:
- Hero на indigo-градиенте: badge «Super Admin Console» + крупный H1
«Системная консоль» + подзаголовок + env+build справа.
- 4 KPI карточки с цветным круглым фоном иконки (indigo/sky/
emerald/amber), label uppercase tracking, значение крупно,
hint мелко серым; hover-shadow.
- Три карточки в ряд (lg:3 cols) вместо дублирующих ссылок-CTA:
«Здоровье системы» (заглушки ✅/Activity для скорых проверок),
«Активные организации» (топ-3 по объёму, ссылка «все»),
«Свежие события» (6 последних audit-записей или empty state
с Inbox-иконкой и текстом «Журнал пуст. Здесь появятся события»).
- Кнопка «Создать организацию» убрана с главной — оставлена
только на /super-admin/organizations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
651038f683 |
ui(super-admin): читаемый логотип на тёмном sidebar
<Logo> получил пропс variant="dark": в нём «FOOD» рендерится белым (slate-50) вместо чёрного, «MARKET» — emerald-400 (вместо var brand) для контраста на indigo-950 фоне SuperAdminLayout. SuperAdminLayout прокидывает variant="dark" в обоих местах (desktop sidebar + mobile header). Светлый вариант остался по умолчанию для tenant AppLayout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
17be1c83b2 |
feat(super-admin): Phase 2b — отдельный SuperAdminLayout и разделение от tenant-админки
Some checks are pending
SuperAdmin при логине больше не попадает на tenant Dashboard (Каталог/Товары/Контрагенты/Выручка) — он стоит над всеми органами, у него отдельная Системная консоль с системным sidebar и системным дашбордом. Каждый из четырёх кейсов теперь визуально различим: 1. SuperAdmin → /super-admin (индиго-сайдбар, системные разделы: Главная / Организации / Пользователи (скоро) / Журнал / Здоровье/Бэкапы/Настройки (скоро)). В topbar — компактный «Открыть организацию ▼» dropdown для быстрого override. Title-suffix « · Super Admin» в browser-tab. 2. Обычный tenant-админ → /dashboard (как раньше). 3. SuperAdmin в режиме «открыть как…» → /dashboard с tenant sidebar'ом + amber-баннер сверху + слабая жёлтая тонировка фона body чтобы периферийным зрением было ясно «не моя админка». 4. SuperAdmin без override руками вбивает /products → TenantRouteGuard редиректит на /super-admin/organizations + alert «Откройте конкретную организацию через "Открыть как…"». Изменения: - SuperAdminLayout.tsx: индиго-палитра, Logo + badge «Super» + подпись «Системная консоль», 7 пунктов меню (5 active + 4 «скоро»), user-карточка с «Super Admin», topbar с org-picker dropdown, redirect на /dashboard если активен override. - TenantRouteGuard.tsx: в useEffect проверяет (SuperAdmin && !override) и редиректит. Сообщение в sessionStorage для AppLayout, alert один раз. - App.tsx: setup wizard вне layout'а; /super-admin/* через SuperAdminLayout (nested routes); остальное через TenantRouteGuard + AppLayout. - AppLayout: убрана 3-пунктовая группа «Супер-админ» — оставил один пункт «Системная консоль» как точку возврата. Тонировка фона amber-50/60 при override. - LoginPage: после успешного login — если me.roles.SuperAdmin и target это /, /dashboard → redirect на /super-admin. - SuperAdminDashboardPage: блок «Последние события» — 10 записей audit-log + ссылка на полный журнал. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7363eb4249 |
ui(roles): warning «изменения применятся ко всем сотрудникам» при edit
Some checks are pending
Edit модалка для кастомной роли (не системной, существующей) теперь показывает amber-плашку перед матрицей прав — напоминает что галка тут касается N людей сразу. Системные роли — без плашки (там disabled чекбоксы). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
01de66493a |
feat(super-admin): Phase 3 — edit-mode с reason + audit-trail
Some checks failed
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
CI / Backend (.NET 8) (push) Successful in 47s
CI / Web (React + Vite) (push) Has been cancelled
Docker API / Build + push API (push) Successful in 44s
Docker API / Deploy API on stage (push) Successful in 17s
В режиме «открыть как…» SuperAdmin может временно (на 30 минут) включить редактирование с обязательной причиной — каждая успешная мутация пишется в SuperAdminAuditLog. Чтобы лог был полезным: API: - Header X-Org-Override-Reason. Если присутствует и trimmed >= 10 символов — ReadonlyOverrideMiddleware пропускает мутации (вместо 403). - SuperAdminEditAuditFilter — глобальный IAsyncActionFilter после controller'а: при наличии обоих headers + успешном статусе 2xx + методе POST/PUT/PATCH/DELETE пишет запись ActionType=EditEntity с reason, описанием «METHOD /path → 200», IP и SuperAdminUserId. Регистрируется как Scoped + AddService<>() в AddControllers. Web: - enableEditMode(reason)/disableEditMode/getEditMode в lib/api.ts — хранение в localStorage с expiresAt = now + 30мин. Axios interceptor добавляет header только пока edit активен и не истёк. - SuperAdminAsOrgBanner расширен: цвет меняется amber→red в edit-mode, кнопка «Включить редактирование» открывает модалку с textarea reason (≥10 символов) + чекбокс согласия на аудит. После активации баннер показывает «EDIT-MODE (N мин)», кнопка «Снять edit» отключает до истечения таймера. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ef32dac0a6 |
feat(super-admin): Phase 2 — read-only «открыть как…» context switch
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
CI / Backend (.NET 8) (push) Successful in 1m7s
CI / Web (React + Vite) (push) Successful in 39s
Docker API / Build + push API (push) Successful in 58s
Docker Web / Build + push Web (push) Successful in 28s
Docker API / Deploy API on stage (push) Has been cancelled
SuperAdmin может зайти в данные конкретной орги в режиме просмотра
(аналог «view as customer» в SaaS). Все запросы летят с tenant'ом
выбранной орги, но любая мутация запрещена — Phase 3 (edit-mode +
audit-trail) будет ослаблять ограничение по reason'у.
API:
- HttpContextTenantContext.OrganizationId: если у юзера роль SuperAdmin
И header X-Org-Override присутствует — возвращаем его как tenant
(вместо org_id из JWT).
- ReadonlyOverrideMiddleware (после UseAuthorization): когда заголовок
активен, отбивает 403 любую non-GET операцию, кроме /api/super-admin/*
(управление орг) и /connect/* (refresh tokens).
- Безопасность: проверка SuperAdmin-роли — без неё header игнорируется,
обычный юзер ничего подменить не может.
Web:
- api.ts: localStorage 'superAdminAsOrg' = {id, name}; axios interceptor
добавляет X-Org-Override на каждый запрос. setOrgOverride(...) делает
hard reload чтобы сбросить TanStack Query-кэш.
- SuperAdminAsOrgBanner — жёлтая полоса сверху main-area с названием
орги и кнопкой «Выйти». Подключена в AppLayout перед <Outlet/>.
- В таблице /super-admin/organizations добавлена кнопка LogIn (синяя)
в actions; клик → setOrgOverride → reload в режим просмотра.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4acb51c270 |
feat(bridge): /quiet и /loud команды для управления PreToolUse прогресс-лентой
- /quiet → создаёт /tmp/cc-tg-quiet, pretool-hook сразу выходит, Stop hook продолжает работать (финальные ответы летят). - /loud → удаляет flag, прогресс-лента возобновляется. - /ping без изменений. На стенде bridge перезапущен, setWebhook 200. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d553933580 |
fix(super-admin): новая org через UI получает полный bootstrap (как Demo)
Some checks failed
POST /api/super-admin/organizations создавал только Store + Admin role в inline-коде — у новой орги не было единиц измерения, типов цен, кастомных ролей шаблонов (Менеджер/Кладовщик/Закупщик/Бухгалтер), кассы. Юзеру приходилось бы заводить всё руками. Решение — переиспользовать DevDataSeeder.SeedTenantReferencesAsync, который уже умеет всё это идемпотентно: - 5 единиц измерения (штука/кг/л/м/упаковка по ОКЕИ) - 2 типа цен (Розничная IsSystem+IsRequired+IsRetail / Оптовая) - «Основной склад» MAIN - «Касса 1» POS-1 - 6 ролей через SeedEmployeeRolesAsync (2 системных + 4 шаблона) Helper повышен с private на public static. В контроллере убран inline Store + AdminRole, после Add(org)+SaveChanges вызывается seed, потом находим уже созданную «Администратор» роль для линковки с Employee. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
982141598a |
feat(infra): PreToolUse hook for Telegram progress feed + rate-limited batching
Hook /usr/local/bin/cc-tg-notify-pretool читает JSON через stdin (tool_name + tool_input) и шлёт короткую строку прогресса в Telegram перед каждым tool-вызовом — чтобы юзер видел «он работает» а не просто ждал финального ответа от Stop hook. Форматы (по требованию ТЗ): - Bash → 🔨 ${description} либо 🔨 Bash: ${command[:80]} - Edit/Write/Read → ✏️/📝/📖 + basename(file_path) - Grep/Glob → 🔍/🌐 + pattern[:30/50] - WebFetch/Search → 🌍/🔎 + url|query[:60] - Task → 🎯 + description[:60] - TodoWrite → skip (шумно) - прочие → 🔧 ${tool_name} Дебаунс через flock + фоновый sleep: - Каждый вызов append'ит строку в /tmp/cc-tg-pretool-buffer.txt и бампит /tmp/cc-tg-pretool-last (epoch ms) под flock'ом. - Спавнит фоновый sleep 1.5s; после пробуждения проверяет, чей timestamp в LAST — если не его, выходит молча. Только «последний» hook реально шлёт батч одним сообщением. - Это даёт пачке tool-вызовов один Telegram-апдейт, а не 5–10. - Если буфер длиннее 20 строк — режется хвостом (свежие важнее). Off-switch: touch /tmp/cc-tg-quiet — pretool-hook сразу выходит. Stop hook продолжает работать. Hook регистрируется в ~/.claude/settings.json под PreToolUse с matcher="" (все tools без фильтра); фильтр TodoWrite — внутри скрипта. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
96772f82c8 |
fix(seed): grant SuperAdmin role to admin@food-market.local
Some checks are pending
Раздел /super-admin в UI прячется за me.roles.includes('SuperAdmin').
Сидер при создании admin'а назначал только SystemRoles.Admin —
SuperAdmin висел как Identity-роль в роле-каталоге, но никому не был
выдан. Из-за этого SuperAdmin-консоль на стенде была не видна в меню.
Фикс: при создании admin'а сразу AddToRoleAsync(SuperAdmin). Для уже
развёрнутых стендов — догоняющая ветка else if (!IsInRoleAsync(SuperAdmin))
догоняет существующую учётку при следующем рестарте API.
На стенде роль уже выдана вручную через INSERT в AspNetUserRoles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
94dbb5b235 |
feat(web): super-admin section + setup wizard + auto-redirect
Some checks are pending
Раздел /super-admin/* доступный только пользователю с ролью SuperAdmin. В сайдбаре отдельная группа «Супер-админ» появляется только если me.roles содержит SuperAdmin (UI-guard, серверная авторизация параллельно проверяется через [Authorize(Roles=\"SuperAdmin\")]). Страницы: - /super-admin — KPI-дашборд (orgs/users/products/supplies) - /super-admin/organizations — таблица всех org с фильтром Активные/ Архив/Все, поиском по Name/BIN. Действия в строке: Архивировать (модалка с вводом названия), Восстановить, Удалить навсегда (только если в архиве >30 дней + повторное подтверждение). - /super-admin/organizations/new — двух-секционная форма создания (реквизиты + первый админ); в response получаем generatedPassword и показываем в одноразовой модалке с copy-кнопками. - /super-admin/audit-log — таблица журнала с экспортом в CSV. - /super-admin/setup — 3-шаговый wizard (welcome → org → admin → done с временным паролем). Авто-редирект на этот URL для SuperAdmin если /api/super-admin/setup-status возвращает needsSetup=true (в системе ноль организаций) — нельзя пропустить пока не создадут. useMe()/useIsSuperAdmin() хуки в lib/useMe.ts — единая точка чтения текущего юзера и его ролей для UI-гейтов. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9482eea050 |
feat(api): super-admin endpoints (orgs CRUD + setup-status + audit-log + dashboard)
Some checks are pending
SuperAdminOrganizationsController (/api/super-admin/organizations):
все методы используют IgnoreQueryFilters() для обхода tenant-фильтра.
- GET / — таблица с пагинацией, фильтр archived, поиск по Name/Bin,
возвращает счётчики (employees, products) + last login по users.
- GET /{id} — детали + статистика (employees, products, counterparties,
supplies за 30 дней) + AccountOwner данные.
- POST / — создание орга вместе с админом: Org + Store «Основной» +
EmployeeRole «Администратор» (IsSystem) + AppUser (random temp pwd
возвращается один раз) + Employee. Owner = созданный AppUser.
- PUT /{id} — правка базовых данных, лог EditOrg с before/after.
- POST /{id}/archive — требует ConfirmationName == Org.Name (ввод).
- POST /{id}/restore — снять архив.
- DELETE /{id} — только если в архиве >30 дней + повторное подтверждение.
- POST /{id}/change-owner — Reason обязателен, валидируем что user
принадлежит этой орге, лог ChangeOwner с from/to.
Все мутации пишут запись в SuperAdminAuditLog с ActionType,
Description, Reason, ChangesJson, IpAddress, SuperAdminUserId.
SuperAdminController (/api/super-admin):
- GET /setup-status — нужен ли wizard? (OrgCount == 0).
- GET /dashboard — total/active/archived orgs, users, products, supplies/month.
- GET /audit-log — фильтры organizationId/actionType/from/to + paged + join
на orgs для имени.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f37a1f12f0 |
feat(domain): Organization.IsArchived/AccountOwner + SuperAdminAuditLog + migration
Some checks are pending
Базовый domain-каркас для SuperAdmin console (Phase 1): Organization: - IsArchived bool + ArchivedAt DateTime? — архивная орга не видна юзерам, но данные сохраняются. Удалить навсегда можно только из архива >30 дней (логика в API на следующем коммите). - AccountOwnerUserId Guid? — главный владелец, не путать с админами per-org. SuperAdmin может сменить через action c reason в audit-log. - HasIndex(IsArchived) для быстрой фильтрации. SuperAdminAuditLog (новая таблица super_admin_audit_log): - Не tenant-scoped — лог общий по всей системе. - ActionType (CreateOrg/EditOrg/ArchiveOrg/RestoreOrg/DeleteOrg/ ChangeOwner/EditEntity), OrganizationId, EntityType+EntityId, Description, Reason, ChangesJson (jsonb), IpAddress. - Индексы: CreatedAt, (SuperAdminUserId, CreatedAt), (OrganizationId, CreatedAt) — типовые запросы фильтра. Migration Phase4_SuperAdminConsole добавляет 3 колонки в organizations + создаёт super_admin_audit_log с тремя композитными индексами. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
77afcdccd0 |
feat(employee): add Salary, TaxNumber, Description, ImageUrl + radio role picker
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 40s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 47s
Docker Web / Build + push Web (push) Successful in 29s
Docker API / Deploy API on stage (push) Successful in 16s
Docker Web / Deploy Web on stage (push) Successful in 11s
Domain Employee расширен 4 nullable-полями (по образу МойСклад): - Salary numeric(18,2) — оклад в валюте организации - TaxNumber varchar(20) — ИИН/ИНН - Description varchar(2000) — комментарий HR'а - ImageUrl varchar(500) — аватар (на будущее: загрузка через images endpoint как у товаров; пока поле для прямой ссылки) Migration Phase4c_EmployeeExtraFields добавляет 4 nullable колонки (существующие записи не ломаются). EF config + snapshot обновлены. API EmployeesController: DTO/Input/Create/Update пробрасывают новые поля сквозь. Frontend EmployeesPage: - Поля «Оклад» и «ИИН/ИНН» рядом, ниже — «Описание» textarea. - Селект роли заменён на radio-список с описанием каждой роли (системные сначала, затем кастомные). Под радио — ссылка «Настроить права ролей →» на /settings/employee-roles. Это по образу МС — пользователь сразу видит за что отвечает каждая роль и куда идти если нужно подкрутить. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
080564f2b2 |
feat(roles): permissions matrix grouped by section + clone-from-template flow
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 41s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Successful in 54s
Docker Web / Build + push Web (push) Successful in 29s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 11s
RolePermissions расширен с 21 до 32 флагов: добавлены UnitsManage, DemandsView/Edit/Post (отгрузка контрагенту, не путать с RetailSales), CounterpartiesDelete, InventoryEdit, LossEdit, EnterEdit (склад-операции), ReportsFinanceView, ReportsStockView (тонкие отчётные права), CashRegistersManage и IntegrationsManage (отдельно от OrgSettingsManage). UI EmployeeRolesPage: - 7 групп вместо 6: Каталог / Закупки / Продажи / Контрагенты / Склад-Остатки / Отчёты / Настройки. Все секции аккордеоном внутри модалки (как было — flex-col), но с правильным грануляр-списком. - Системные роли — чекбоксы disabled (только просмотр; имя/описание редактируются). - При [+ Добавить роль] — сначала открывается модалка выбора шаблона: Пустой / Копия Администратора / Копия любой существующей. Дальше открывается основная модалка с предзаполненной матрицей. allPerms() помощник на фронте — зеркало RolePermissions.All() с бэка, для шаблона «Копия Администратора» в clone-flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dd3bf5e20f |
fix(roles): keep only Admin + Cashier as system, demote others to custom + migration
Some checks are pending
После ревью UX оказалось что 6 системных ролей — перебор. Перешли на схему «два системных + остальные шаблоны»: - Администратор (IsSystem=true) — RolePermissions.All(). - Кассир (IsSystem=true) — POS-only набор: ProductsView + StocksView + RetailSalesOperate. Без RetailSalesRefund (админ включит при необходимости). Это маркер для будущего POS-app — не имеет доступа к веб-админке. - Менеджер / Кладовщик / Закупщик / Бухгалтер — IsSystem=false (кастомные). Можно удалить если не нужны или подкрутить под себя. Сидер на чистой БД сразу создаёт роли в правильных статусах. Для существующих установок миграция Phase4b_RolesSimplify идемпотентно делает UPDATE: демоутит лишние и приводит permissions Кассира к правильному набору. Down() — no-op (юзер мог переименовать). На стенде sql применил вручную + записал в __EFMigrationsHistory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7c40c11595 |
feat(infra): event-driven Telegram bridge — webhook + Stop hook
Полный отказ от 2-секундного polling tmux'а в пользу реактивной схемы:
OUTBOUND (server-Claude → Telegram) через Stop hook:
- /usr/local/bin/cc-tg-notify-stop (Bash) читает transcript из stdin
(Claude Code передаёт {transcript_path}), достаёт последнюю
assistant-запись с непустым text-блоком (jq), чанкует ≤4000 символов
с префиксом «🤖 [food-market]», POST'ит в Telegram через curl.
Логи /var/log/cc-tg-notify.log. Если turn без текстового ответа
(только tool calls) — выходит молча.
- Зарегистрирован в ~/.claude/settings.json под Stop event с пустым
matcher (все turns).
INBOUND (Telegram → bridge → tmux) через webhook:
- bridge.py переписан с run_polling на run_webhook listening
127.0.0.1:8765 на /tg-webhook. python-telegram-bot[webhooks]
(tornado) ставится через pip.
- При старте сам делает setWebhook к Telegram API с secret_token
из TELEGRAM_WEBHOOK_SECRET (osprandom 24 hex), Telegram присылает
его обратно в X-Telegram-Bot-Api-Secret-Token — PTB валидирует
до вызова handler'ов.
- Сохранены: whitelist по chat_id, paste-в-tmux через
send-keys -l + Enter, /ping команда. Удалён poll_and_forward,
diff/clean логика, recently_sent_lines дедуп — больше не нужны.
Nginx: новый location = /tg-webhook на food-market-stage.conf,
проксирует на 127.0.0.1:8765 с прокидыванием X-Telegram-Bot-Api-
Secret-Token. Smoke-test: curl с неверным секретом → 403.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
08f03fd17a |
fix(migrations): drop Employee.Navigation(RetailPointAssignments) to fix snapshot order
Some checks are pending
В snapshot/Designer я вручную добавил b.Navigation(\"RetailPointAssignments\") в блоке Employee — но эта обратная навигация регистрируется через WithMany(\"RetailPointAssignments\") у EmployeeRetailPointAssignment.HasOne(...), который выполняется ПОЗЖЕ. Из-за этого BuildTargetModel падал с «Navigation … was not found», и API на стенде не мог применить миграцию. Убрал лишнюю строку в обоих местах. Свойство Employee.RetailPointAssignments никуда не делось — обратную навигацию EF создаёт автоматически из конфига EmployeeRetailPointAssignment. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8fb55993a1 |
feat(onboarding): welcome dashboard with first-steps cards
Some checks failed
Дефолтная страница после логина (/) — OnboardingPage по образу
МойСклад «Первые шаги». Старый DashboardPage с KPI и графиком
переехал на /dashboard, в меню «Главная» теперь два пункта:
«Главная» (онбординг) и «Аналитика» (KPI/графики).
useOnboardingProgress() — хук, считает 4 шага:
- orgConfigured: country + defaultCurrency установлены
- hasEmployees: > 1 сотрудник (помимо админа)
- hasProducts: > 0 товаров
- hasSupplies: > 0 приёмок
OnboardingPage:
- Прогресс-бар «N из 4 шагов» с процентом
- 4 карточки задач: Настройки → Сотрудники → Каталог/Импорт → Приёмка
- Каждая показывает иконку (CheckCircle2 если done) + бэйдж
категории + заголовок + описание + CTA-кнопка с ArrowRight,
меняющая текст и ссылку в зависимости от done.
- Когда все 4 шага сделаны — плашка «🎉 Готово!» + переход на
/dashboard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
033f20e215 |
feat(web): Employees + Roles pages with permissions matrix
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
CI / Backend (.NET 8) (push) Successful in 42s
CI / Web (React + Vite) (push) Successful in 38s
Docker Web / Build + push Web (push) Has been cancelled
EmployeesPage (/settings/employees): - Таблица: ФИО + должность, Роль, Email, Телефон, Учётка (есть/нет), Статус (Активен/Уволен). - Модалка добавления: ФИО + Position + Email + Phone + Role. Если выбрана роль «Кассир» — появляется блок «Кассы» с чекбоксами привязки к RetailPoint'ам (multi-select). - Чекбокс «Создать учётную запись» (по умолчанию ✓): сервер возвращает generatedPassword один раз, показываем в отдельной модалке с copy-кнопками логина и временного пароля. - Update/Delete как обычно. Снять Активен → серверная установка FiredAt. EmployeeRolesPage (/settings/employee-roles): - Таблица системных + кастомных ролей с счётчиком активных прав (N/21). Системные помечены бэйджем «Системная». - Модалка edit: имя, описание, матрица прав сгруппированная по 6 блокам (Каталог/Закупки/Продажи/Контрагенты/Отчёты/Настройки). Удаление кнопка только для кастомных. Меню «Настройки организации» дополнено пунктами «Сотрудники» (иконка UserCog) и «Роли» (иконка Shield). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
062eb44fbb |
feat(api): EmployeesController + EmployeeRolesController + invite-with-temp-password
Some checks failed
EmployeeRolesController (/api/organization/employee-roles):
- List/Get/Create/Update/Delete. Системные роли (IsSystem=true) — нельзя
удалить (409), но имя/описание/permissions редактируются (чтобы можно
было кастомизировать набор галок). Удаление 409 если роль уже
используется сотрудниками.
EmployeesController (/api/organization/employees):
- List с поиском по фамилии/имени/email/телефону.
- Create:
- LastName, FirstName, MiddleName, Position, Email, Phone, RoleId, IsActive
- RetailPointIds[] — для роли Кассир привязка к нескольким кассам;
хранится в employee_retail_point_assignments.
- CreateAccount=true → одновременно создаём User (Identity) с email и
случайным temp-паролем (12 символов, все классы), возвращаем в
response.GeneratedPassword один раз — UI покажет «выдайте сотруднику».
- Update — replace assignments wholesale; IsActive false → проставляем
FiredAt=now (восстановление обнуляет).
- Delete — без проверок на FK документов (на этом этапе нет других
ссылок на Employee, кроме CASCADE-связи с retail-point assignments).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b40d1d9835 |
feat(seed): system roles per organization + map admin → Employee
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
CI / Backend (.NET 8) (push) Successful in 40s
CI / Web (React + Vite) (push) Successful in 38s
Docker API / Build + push API (push) Has been cancelled
DevDataSeeder.SeedEmployeeRolesAsync — 6 системных ролей с готовыми наборами Permissions: - Администратор — RolePermissions.All() (все 21 флаг) - Менеджер — каталог + закупки + контрагенты + отчёты + остатки - Кладовщик — приёмки + остатки + view товаров - Кассир — продажи + view товаров (привязка к кассе на UI-этапе) - Закупщик — закупки + контрагенты + view товаров - Бухгалтер — все *View, никаких edit IsSystem=true, SortOrder сохраняет порядок отображения в селектах. Сидируется один раз per организацию (anyRole? skip) — чтобы кастомные правки галок админа не сбрасывались на каждый старт. SeedAdminEmployeeAsync — после создания admin@food-market.local (SuperAdmin Identity user) заводит Employee-запись с ролью «Администратор» в Demo Market организации, чтобы UI «Сотрудники» сразу показывал учётку, а не пустой список. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
33234f5e44 |
feat(domain): Employee, EmployeeRole, RolePermissions entities + migration
Some checks failed
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
CI / Backend (.NET 8) (push) Successful in 42s
CI / Web (React + Vite) (push) Has been cancelled
Базовый каркас модуля «Сотрудники и Роли» (по образу МойСклад):
Domain:
- Employee — сотрудник организации (UserId nullable: запись может
существовать без логина), ФИО + Position + Email/Phone + Role + IsActive
+ FiredAt + RetailPointAssignments.
- EmployeeRole — роль с IsSystem флагом и owned RolePermissions.
- RolePermissions — 21 булев флаг по группам (Каталог/Закупки/Продажи/
Контрагенты/Отчёты/Настройки) + helper All() для админа.
- EmployeeRetailPointAssignment — ассоциация сотрудника с RetailPoint
(для роли Кассир — к каким кассам привязан).
Infrastructure:
- OrganizationsHrConfigurations с OwnsOne(...).ToJson("permissions")
для permissions — JSONB-колонка вместо отдельной таблицы.
- DbSet<EmployeeRole/Employee/EmployeeRetailPointAssignment>.
- Уникальные индексы: (OrgId, RoleName), (OrgId, UserId) с filter
WHERE UserId IS NOT NULL, (EmployeeId, RetailPointId).
Migration Phase4_EmployeesAndRoles создаёт три таблицы. Сидер
системных ролей и привязка существующего admin'а к Employee —
следующим коммитом, контроллеры и UI — далее.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d271cd7410 |
refactor(retail-points): rename «Точка продаж» → «Касса» + перенос
Some checks are pending
складов и касс в раздел «Настройки организации»; useOrgInfra хук UI-переименование: - RetailPointsPage: title «Кассы», description обновлён, лейблы «Новая касса» / «Удалить кассу?»; доменная сущность RetailPoint и URL /api/catalog/retail-points сохранены — DTO/БД не трогаем. - В сайдбаре пункты «Склады» и «Кассы» перенесены из бывшей группы «Склады» в группу «Настройки организации» (рядом с «Общие»). Старые пункты верхнего уровня убраны. useOrgInfra() — общий хук: - возвращает stores, cashRegisters, defaultStoreId, defaultCashId - showStorePicker / showCashPicker = length > 1 (умное скрытие селекторов в формах документов когда инфра одна). В SupplyEditPage скрытие склада уже работало через (stores.data?.length ?? 0) > 1 — оставил как есть, новый хук для будущих документов (продажи, инвентаризации). Сидер default Store + RetailPoint per Organization уже есть в DevDataSeeder.cs (Основной склад MAIN + Касса 1 POS-1) — дополнять не нужно. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d447f431ba |
fix(searchable-select): dropdown opens as floating overlay (Portal + absolute)
Some checks are pending
Старый dropdown использовал position:absolute внутри Section (а у того overflow-hidden для скруглений), из-за чего он клипался границами карточки. На некоторых страницах визуально это смотрелось так, будто список «раздвигает» layout. Решение — тот же Portal-паттерн что у SupplyLineQuickAdd и DateField: - dropdown рендерится через createPortal в document.body - position: fixed, координаты по getBoundingClientRect() trigger'а - z-[100], sticky-headers/секции не перекроют - Auto-flip: если внизу <240px и сверху больше — открываем вверх (anchor через bottom: window.innerHeight - rect.top) - Outside-click учитывает обе ноды (wrap + dropdown в portal) - На window.scroll/resize dropdown закрывается чтобы не «уплывать» относительно trigger'а Фиксит сразу все Select'ы в проекте (тип штрихкода, валюта, страна, группа товара, тип цены и т.д.) — компонент один. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
37bacc196e |
fix(date-field): theme styles + default to today for new docs
Some checks are pending
Тема react-datepicker подогнана под Tailwind/slate-стиль остального UI: - font-family inherit, шрифт системный (не Helvetica дефолт); - header bg-slate-50, border slate-200, скругления 0.5rem; - выбранный день — заливка var(--color-brand) (фирменный зелёный); - сегодня — bold + brand color; - день под клавиатурой — slate-100 без заливки brand; - стрелки навигации серые slate-500 (без bootstrap-синего); - footer «Сегодня» — приглушённый slate-50 с brand-цветом текста; - треугольник-указатель скрыт; - крестик clear — slate-400 с hover slate-500; - весь dark-вариант через prefers-color-scheme — slate-800/900. todayIso() в SupplyEditPage переписан с toISOString() (UTC) на локальный YYYY-MM-DD — иначе в часовом поясе KZ (UTC+5) с 03:00 до 05:00 утра новая приёмка получала бы вчерашнюю дату. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d28c6e703a |
feat(date-field): replace native input with react-datepicker — polished UX
Some checks are pending
Нативный <input type=\"date\"> рендерил американский MM/DD/YYYY и тонкий браузерный popup, выглядел криво рядом с другими полями. Используем готовый react-datepicker (10M downloads/week) — никакой кастомизации, всё из коробки: - dateFormat=\"dd.MM.yyyy\" + locale=\"ru\" → «25.04.2026», русские Январь/Понедельник - showMonthDropdown + showYearDropdown + dropdownMode=\"select\" → быстрый прыжок на любой месяц/год - todayButton=\"Сегодня\" → кнопка под календарём - isClearable → крестик в input для очистки - popperClassName z-[100] чтобы попап не резался z-стеком ISO YYYY-MM-DD внутрь/наружу собираем вручную из локальных Y/M/D — не toISOString(), чтобы вечерние даты в часовом поясе KZ (UTC+5) не сдвигались на день назад. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
33e1572c3a |
revert(date-field): drop custom react-day-picker — use native input type=date
Some checks are pending
Кастомный DateField на react-day-picker оказался хрупким (стрелки навигации не реагировали, dropdown лет вылазил за popover). В проекте нет shadcn/ui-обёртки над day-picker, а пилить её с нуля под одно поле — overkill. Откатил на нативный <input type=\"date\"> с max-w-[180px], чтобы поле не растягивалось на всю колонку. Браузер сам подтягивает локаль из ОС/настроек — у пользователя с RU-локалью календарь будет на русском, формат DD.MM.YYYY (как в его референс-скриншоте). - Удалён components/DateField.tsx. - В SupplyEditPage возвращён <TextInput type=\"date\"> с className=\"max-w-[180px]\". - Сняты зависимости react-day-picker и date-fns. Если когда-нибудь вернёмся к кастомному picker'у — будем ставить shadcn/ui calendar+popover целиком, не вручную. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
88e382d9d7 |
fix(date-field): polish calendar UX — dropdown nav, today/clear footer, ru weekdays
Some checks are pending
Календарь приведён к виду нативного date-picker macOS: - captionLayout="dropdown" + startMonth/endMonth — в шапке селекты «Апрель» и «2026» (можно прыгнуть на любой месяц/год без 12 кликов next). - Стрелки навигации справа в шапке (absolute right-1 top-1), компактные 28×28, hover-bg, без ярко-синего акцента. - footer prop — две inline-ссылки «Очистить» (сбрасывает) и «Сегодня» (ставит сегодняшнюю дату); border-t над ними. - Сегодня = синяя заливка bg-brand text-white (как на референсе); выбранная дата = ring-2 ring-brand; если сегодня = выбранный — применяются обе (сначала заливка, потом ring). - Ширина popover'а w-[340px], ячейки 36×36, weekday и dropdown capitalize → «Пн Вт Ср …», «Апрель 2026». Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
22cc0256b9 |
fix(date-field): compact calendar popup — shadcn-style sizing
Some checks are pending
Календарь react-day-picker раздувался на ~700px из-за дефолтных стилей. Теперь plотный shadcn-style ~280–290px: - CSS-переменные v9 на враппере: --rdp-day-height/width=2rem, --rdp-day_button-height/width=2rem, --rdp-weekday-padding=0, --rdp-accent-color=brand. Контейнер p-3 text-sm. - classNames переписаны: ячейка дня h-8 w-8 (32×32) с rounded-md, weekday w-8 capitalize, caption_label text-sm font-medium. Кнопки навигации 24×24 с hover-bg, chevron 16×16. - Сегодня: bg-slate-100 + font-semibold (без жирной обводки). Выбранная дата: bg-brand text-white. Outside-дни выцветшие. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e731626390 |
fix(date-fields): cap width + ru locale + DD.MM.YYYY format
Some checks are pending
Новый компонент <DateField/>: - Ширина зафиксирована (по умолчанию w-40 = 160px) — раньше нативный <input type="date"> растягивался на всю колонку, хотя содержимое всегда 10 символов. - Ввод в формате DD.MM.YYYY с авто-вставкой точек после dd и mm, inputMode=numeric для мобилы. Хранит/отдаёт ISO YYYY-MM-DD — API-контракт не меняется. - Иконка календаря справа открывает попап (через portal в body, position fixed) с react-day-picker: locale=ru, weekStartsOn=1, ISOWeek; caption_label/weekday с capitalize CSS — «Апр 2026», «Пн Вт Ср …». Outside-click закрывает. Подключено в SupplyEditPage (поле «Дата»). Ставка на единый компонент DateField — все будущие даты в системе через него. Зависимости: + react-day-picker ^9, + date-fns ^4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
290a95c54c |
fix(supply-lines): show both article and barcode in line subtitle
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 42s
CI / Web (React + Vite) (push) Successful in 35s
Docker API / Build + push API (push) Successful in 44s
Docker Web / Build + push Web (push) Successful in 26s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 11s
В таблице позиций приёмки под названием товара теперь выводится и артикул, и основной штрихкод сразу — раньше показывалось что-то одно (артикул или ничего, без штрихкода). Формат: «Арт: 17933 · ШК: 4870144022958». Если только одно из двух — префикс соответствующий, без точки-разделителя. Если ни того ни другого — subtitle не рендерится. Шрифт мелкий моно серый. API: - SupplyLineDto расширен полем ProductBarcode (основной по IsPrimary, иначе первый по порядку). - В проекции GetInternal штрихкод подтягивается через p.Barcodes.OrderByDescending(IsPrimary).Select(Code).First(). Frontend: - types.ts.SupplyLineDto, LineRow в SupplyEditPage и AddedProduct в SupplyLineQuickAdd получили поле productBarcode/barcode. - При добавлении строки через ProductPicker, sticky-input или quick-create — primary barcode достаётся из p.barcodes одинаковой логикой (sort by IsPrimary desc, [0]). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f9a17ad5c2 |
fix(supply-quick-add): sticky input at viewport bottom + auto-scroll on add
Some checks are pending
Quick-add bar теперь не sticky-внутри-Section, а отдельный flex-sibling
формы — всегда прибит к нижнему краю viewport независимо от высоты
содержимого и overflow-hidden у Section'а:
<form flex flex-col h-full>
<topbar />
<body flex-1 overflow-auto> ← скроллится
<quick-add bar flex-shrink-0> ← всегда виден
</form>
После каждого добавления строки скролл-контейнер тела документа
автоскроллится к низу (smooth, через requestAnimationFrame чтобы
дождаться рендера новой строки) — новая строка всегда появляется
прямо над input'ом и пользователь видит подтверждение скана.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
cad6b32f5e |
fix(supply-quick-add): dropdown opens upward + show only N results + create-new at bottom
Some checks are pending
UX как в МойСкладе:
- Dropdown открывается ВВЕРХ от input'а (anchor по input.top, fixed
bottom). Max-height 60vh — не перекрывает шапку, внутри overflow-y.
Раньше выпадал вниз и при добавлении строк визуально не оставалось
места.
- Показываем первые 10 матчей (VISIBLE_LIMIT=10), под ними ссылка
«Ещё N товаров» которая раскрывает полный список (limit=50 на
сервере). На запрос приходит до 50 — этого достаточно для
подавляющего большинства поисков.
- Под списком (через тонкий разделитель) — пункт «+ Создать новый
товар: «{q}»» / «Создать товар со штрихкодом «…»». Всегда последний
визуально, ближайший к input'у — самый удобный для быстрого клика.
Стрелки ↑↓ работают по видимой части (1..10 или весь список после
expand). Enter подбирает подсвеченный из видимой части.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
6f839bf57a |
feat(product-card+list): drop supplier field, reorder sections, add cost column
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 44s
CI / Web (React + Vite) (push) Successful in 34s
Docker API / Build + push API (push) Successful in 44s
Docker Web / Build + push Web (push) Successful in 27s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Карточка товара: - убрано поле «Основной поставщик» из секции «Классификация» (домен/DTO оставлены без изменений; в payload отправляется null); - порядок секций: Основное → Цены → Классификация → Изображения → Штрихкоды (раньше Цены шли после Классификации). Цены — самое важное, должны быть ближе к названию товара. Список товаров: - добавлена колонка «Себестоимость» перед колонкой системной розничной цены. Источник — Product.Cost (скользящее среднее, обновляется при проведении приёмки). Cost = 0 (приёмок не было) показывается как «—», чтобы визуально отличать «не накопилось» от реальной себестоимости 0. - API: добавлен сортировочный case sort=cost,asc/desc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
45f2ce682f |
fix(supply-quick-add): keep input focused after scan / clear on add
Some checks are pending
Сценарий — приёмщик подряд сканирует 50 штрихкодов без клика мышью:
- Sticky-bar с input'ом теперь ВНЕ Section'а (overflow-hidden родителя
ломал sticky), прибит к низу скроллируемого тела документа. После
любого добавления строки — input всегда виден.
- Очистка query и refocus вызываются СРАЗУ после клика/Enter, до
await на /products/{id}: пока сеть в полёте, юзер уже может начать
следующий скан. После завершения запроса — повторный refocus
(двойной guarded focus + requestAnimationFrame), чтобы перебить
любые ререндеры родителя, которые могут увести фокус.
- Поиск по quick-search теперь через AbortController — устаревший
ответ при быстром вводе подряд не подменяет свежий список.
- Закрытие ProductQuickCreateModal тоже возвращает фокус в input
(и при «Создать», и при «Отмена»).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c8a7efde47 |
fix(supply-quick-add): dropdown not rendering — Portal + fixed position
Some checks are pending
Корень проблемы: Section рендерится с класcом overflow-hidden (нужен для скруглений углов) — absolute-позиционированный dropdown обрезался границей карточки и был не виден совсем. Решение: dropdown через React createPortal вынесен в document.body, позиция вычисляется по getBoundingClientRect() input'а на каждый open + window scroll/resize. position: fixed, z-[100] — выше любого sticky-header. Outside-click handler теперь учитывает оба контейнера (input wrap + portal-узел) — клик по элементу dropdown'а больше не закрывает его как «снаружи». Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |