Commit Graph

98 Commits

Author SHA1 Message Date
701af06290 feat(billing): PaymentProvider + Mock, self-service пополнение баланса
Шаг 0.13 плана production-готовности:
- интерфейс PaymentProvider (createPayment/verifyWebhook/getPaymentStatus)
- MockPaymentProvider с настоящим webhook (sha256-подпись, идемпотентность)
- applySucceededPayment: транзакция с guard, credit, авто-снятие billingBlocked
- роуты payments + confirm-test (только mock) + публичный webhook
- Billing.tsx: Пополнить/Подтвердить (тест), фикс setLocation в рендере
- миграция 0080 billing_payments, 13 новых тестов (78/78)
2026-09-07 22:45:30 +03:00
23c49adcd1 perf(access): TTL-кэш getAccessibleTaskIds с точечной инвалидацией
Шаг 0.12 плана production-готовности:
- accessibleTasksCache (45с) per userId:orgId внутри storage-метода
- инвалидация в storage-слое: роли, делегирования, form access,
  assignees, createTask/updateTask, appRole пользователя
- 9 новых юнит-тестов (65/65 зелёные)
2026-09-07 22:27:33 +03:00
a27766a38b perf(tasks): лимиты/пагинация /api/tasks и дельта-синк в SQL
Шаги 0.10 и 0.9 плана production-готовности:
- дефолт limit 50, кап 200; списковые ответы без description
- курсорная пагинация (updated_at, id) + nextCursor
- since-фильтр дельта-синка перенесён в SQL (updated_at >= since)
- embedding pre-count через COUNT(*) вместо выгрузки миллиона строк
- 11 новых юнит-тестов (56/56 зелёные)

Инвариант поиска сохранён: search-ветка /api/tasks?search= не изменена
2026-09-07 22:01:25 +03:00
5426c70f1f fix(deploy): vite.config импортируется по вычисляемому URL — esbuild без --splitting
esbuild без --splitting инлайнит статический import('../vite.config') в бандл
и поднимает его импорты (vite, @vitejs/plugin-react) на верхний уровень —
prod падал с ERR_MODULE_NOT_FOUND при старте. Динамический import по
вычисляемому URL esbuild не анализирует и оставляет рантайму (dev-only).

Проверено: dist/index.js содержит 0 статических импортов vite, бандл стартует,
подключается к БД и доходит до инициализации таблиц
2026-09-07 21:33:58 +03:00
9fc6ad1bd1 fix(deploy): prod-бандл без devDependencies — динамический импорт vite в setupVite
Шаги 0.5/0.2 плана production-готовности (досрочно ушли с a54ec77):
- Dockerfile: npm ci --omit=dev вместо копирования node_modules из builder,
  пользователь app (USER app), HOME=/tmp, npm_config_cache=/tmp/.npm
- docker/Dockerfile.documents: USER app, HOME=/tmp
- server/vite.ts: vite и vite.config импортируются динамически внутри setupVite —
  иначе prod-бандл падал с ERR_MODULE_NOT_FOUND (@vitejs/plugin-react)
- package.json: vitest/supertest/@types/* (16 пакетов) перенесены
  в devDependencies — именно vitest в dependencies тянул vite/esbuild/tsx в прод-образ

Проверено локально: образ собирается, uid=1001(app), /app/data доступна на запись,
бандл стартует без devDeps (падает только на отсутствии JWT-секретов окружения)
2026-09-07 21:22:27 +03:00
cf6f937108 security(users): SafeUser — passwordHash/токены не уходят клиенту
Шаг 0.8 плана production-готовности:
- shared/schema.ts: тип SafeUser + safeUserColumns
- getUsersByOrganization, listUsers, searchUsers, getUsersByRole,
  getUsersByOrgRoleId, documents getUser — без чувствительных колонок
- sync (initial/delta), автоматизации ctx.users.list, MCP list_users — sanitized
2026-09-07 21:03:00 +03:00
26419f395f security(errors): 5xx без утечки err.message клиенту
Шаг 0.7 плана production-готовности:
- глобальный error handler: нейтральный текст + код инцидента, детали в лог
- route-уровень: зачистка err.message/String(err) в 5xx по 12 файлам,
  оригинальные ошибки логируются с контекстом
2026-09-07 20:42:39 +03:00
b3d7923b23 test(ci): скрипт test, тесты в pr-check, фикс sporadic 403 в trackUserActivity
Шаг A3 (из 1.6) плана production-готовности:
- package.json: скрипт "test": "vitest run"
- pr-check.yml: шаг Tests после type check
- server/utils/userActivity.ts: try/catch в trackUserActivity — синхронная
  ошибка трекинга активности больше не срывает authenticateToken (ложный 403
  на первом запросе пользователя за минуту)
- tests/auto-transitions.test.ts: 3 устаревших теста обновлены под намеренное
  поведение (forward-only position, синк assignee, аудит вместо системного сообщения)

45/45 тестов зелёные, npm run check чисто.
2026-09-07 20:28:44 +03:00
66f4e5ef53 DI2: удаление старого модуля финансов — пул БД и аудит перенесены в finance-di2
- server/finance/ (19 файлов) удалён: пул финансовой БД перенесён в server/finance-di2/db-core.ts, db-client.ts теперь реэкспортирует из ./db-core
- Шедулер аудита перенесён в server/finance-di2/audit-scheduler.ts (03:00 МСК, setInterval, без node-cron), запуск из registerDi2Routes с защитой от повторного старта
- История аудита DI2 теперь персистентна: загрузка/запись data/finance-audit-history.json в audit-agent.ts (порт из удалённого server/finance/audit.ts)
- registerFinanceRoutes убран из server/routes/index.ts, registerDi2Routes остался на его месте
- Клиент: удалены pages/finance/, components/finance/, hooks/useFinance.ts, hooks/useColumnLabels.ts, lib/format.ts, pages/salary.tsx; роуты /finance*, /income*, /expenses*, /salary и lazy-импорты убраны из App.tsx
- Навигация: «Финансы» в Sidebar и MobileBottomNav ведёт на субапп /di2 (внешняя ссылка), гейт moduleAccess.finance + finance.view сохранён
- index.css: удалён блок .hover-elevate/.hover-elevate-2/.active-elevate (использовался только старым модульным DataTable)
- FinanceBottomNav.tsx сохранён как референс для этапа 3 (.superpowers/sdd/di2-restyle/FinanceBottomNav.reference.tsx)
- Не тронуто: client-di2/, сиды прав finance.view/manage, finance_access (module-access)
2026-09-06 21:53:47 +03:00
49578ab442 DI2 фикс коллизии: дашборд DI2 переименован в /api/di2/dashboard
DI2-роутер регистрируется перед основным dashboard.routes.ts и перехватывал
GET /api/dashboard (403 без finance.manage и неверный JSON — live-регрессия
главного дашборда CRM). Роут переименован, в DI2_PREFIXES /api/dashboard
заменён на /api/di2, клиент client-di2 ходит на новый путь (fetch + queryKey
+ prefetch в app-sidebar). Форматы старого модуля финансов не тронуты.
2026-09-06 19:58:24 +03:00
254950f151 DI2 внедрение, таск 1: серверная интеграция Data-Insight2 (server/finance-di2)
- Копия DI2-сервера в server/finance-di2/ с правками: schema/db-client/cache/table-config/override-tables/google-auth/audit-agent/routes
- db-client — обёртка над существующим пулом server/finance/db-client (второй пул не создаётся, добавлен экспорт isConnectionError)
- ai-config.ts — чистое IO конфигов из ai-agent.ts без Telegram-поллинга; роуты /api/ai/toggle и /api/ai/status удалены, без compression/startAuditScheduler/autoStartIfEnabled
- Обёртка registerDi2Routes с auth-gate (authenticateToken + finance.manage), регистрация строго перед registerFinanceRoutes
- Статика /di2 из dist/public-di2 перед веткой vite/static (dev и prod)
- Фикс предсуществующего бага DI2: buildGlobalExclusionConditions без cats в /api/profitability
2026-09-06 19:27:43 +03:00
a60a75eda0 feat(profile): read-first редизайн страницы пользователя, per-field как в задачах
- Раскладка: шапка-профиль → Основное/Контакты → Роль и доступ/Поля+Замещение → вторичные разделы чипами
- ProfileFieldRow: двойной клик/карандаш, редактор в слоте, Esc, optimistic-кэш, все типы полей
- Замещение: надпись + ⊕ popover, список под строкой
- Удалены RHF-форма, zod-схема и кнопки «Сохранить»
- server: PATCH /api/users/:id (частичное обновление, admin-градация, аудит)
- server: history-number в REST PATCH пишет историю + пересчёт средней
2026-09-06 18:47:05 +03:00
ddc2bd9491 Поля профиля с options.readOnly: запрет редактирования «Оценки» и «Средней оценки»
- UI: read-only поля отображаются текстом (у history-number остаётся кнопка графика),
  исключены из сохранения; кнопка «Сохранить» скрыта, если все поля вкладки read-only
- Сервер: POST/PATCH field-values профиля отклоняют запись в поле с options.readOnly (400);
  автоматизации пишут через storage напрямую и не затрагиваются
- Данные: options.readOnly=true у полей «Оценка» и «Средняя оценка»
2026-09-06 15:08:23 +03:00
b070f862c5 Sandbox ctx.tasks.setFieldValue: передача formId в createTaskFieldValue
task_field_values.form_id NOT NULL — insert без formId падал, значение поля не создавалось
2026-09-06 13:46:39 +03:00
2960879fa9 Sandbox: ctx.tasks.setFieldValue искал несуществующий fieldCode + логирование триггерных автоматизаций
- automation-worker-code: getFormFields возвращает поле code (не fieldCode) — поиск поля всегда завершался ошибкой 'Field ... not found', ошибка терялась
- automation.routes: результаты runAutomationsByTrigger (логи, ошибки) теперь пишутся в консоль сервиса с префиксом [automation]
2026-09-06 13:37:44 +03:00
0bde7764b1 MCP create_task: создание исполнителя в task_assignees при assigned_to
Раньше assigned_to писался только в legacy-колонку tasks.assigned_to, а запись
в task_assignees (модель исполнителей) не создавалась — из-за этого автоматизации,
читающие ctx.tasks.getAssignees (например «Ответственный в состав бригады»),
видели пустой список исполнителей для задач, созданных через MCP
2026-09-06 13:10:27 +03:00
ec86fa4c11 Рейтинг сотрудников: history-number поле = последняя оценка, среднее за месяц — в поле options.avgFieldCode
- setUserProfileFieldValueFromAutomation: для history-number значением поля становится переданное число (последняя оценка), а не среднее
- recomputeUserFieldMonthlyAverage: среднее за текущий месяц пишется в поле, указанное в options.avgFieldCode history-number поля (если не задано — в само поле, обратная совместимость)
- Автоматизация «Оценка сотрудникам» (форма 11): сумма трёх числовых полей «Оценка 1/2/3» вместо одного select-поля
- Документация sandbox обновлена во всех четырёх местах (UI, MCP, Obsidian, AGENTS.md)
2026-09-06 11:55:15 +03:00
a7a14d947a Финансы: деревья категорий как в Data-Insight2 — группировка авто/ручные, свёрнуто по умолчанию, полосы сумм, нераспределённые 2026-09-06 09:53:45 +03:00
2d137332da Финансы: дебиторка по умолчанию читает из таблицы prosrochennayadebitorskayazadolzhennost — view vw_debt_active отсутствует во внешней БД 2026-09-06 09:40:56 +03:00
7a8db03163 Финансы: дебиторка (vw_debt_active), расчёты НДС/налога на прибыль, аудит целостности с автопланировщиком, синхронизация зарплат из Google Sheets, миграция настроек Data-Insight2 2026-09-05 15:07:36 +03:00
005a51e367 fix: MCP update_task_status запускает автоматизации task.status_changed (раньше обходил executeTaskTransition) 2026-09-04 20:32:06 +03:00
ba723aa0b7 feat: ctx.users.recalcFieldAverage + документация автопересчёта среднего history-number 2026-09-04 20:14:51 +03:00
6aececeb4a feat: автоматический пересчёт среднего для history-number в sandbox
- setFieldValue для history-number: переданное число пишется в историю, значением поля становится среднее за текущий месяц (автопересчёт)
- ctx.users.recalcFieldAverage(userId, fieldCodeOrId): принудительный пересчёт среднего за месяц (для schedule-автоматизаций; нет записей — поле очищается)
- документация обновлена (Automations.tsx, mcp.ts)
2026-09-04 20:09:27 +03:00
4d65049e02 feat: рейтинговые автоматизации, мультивыбор user/role-полей, график history-number
Инфраструктура автоматизаций:
- триггер task.status_changed оживлён: вызов из executeTaskTransition (fire-and-forget) + фильтр triggerConfig.statusId
- ctx.users.setFieldValue(userId, fieldCodeOrId, value): запись user_profile_field_values с аудитом; для history-number — append в user_field_history (миграция 0078)
- документация sandbox обновлена (Automations.tsx, mcp.ts, AGENTS.md) по критическому правилу 2

Мультивыбор пользователей и ролей в user-поле:
- чекбокс «Множественный выбор (пользователи и роли)» в настройках поля (form_fields.allow_multiple)
- значение — массив ['user:ID','role:ID'], обратная совместимость с одиночными/legacy значениями
- UserRoleMultiSelect в карточке задачи, реестре и диалоге создания (роли добавлены и в одиночный сценарий создания)
- синк в task_assignees итерирует массив; role:ID раскрывается в членов роли; PATCH single — diff старого/нового значений
- отображение массивов: реестр, title-шаблоны, условия полей, MCP update_task_fields
- список орг-ролей (/api/roles) загружается всем пользователям (сервер и так был открыт) — роли доступны не только админам

График history-number в профиле:
- GET /api/users/:id/field-history/:fieldId?period=week|month|quarter|year (агрегация AVG по бакетам)
- диалог с LineChart (recharts) и переключателем периодов по клику на иконку у поля
2026-09-04 19:39:10 +03:00
67dbf90d65 feat(gps): мосты-предположения по дорогам между GPS-якорями на LBS-участках (OSRM route, пунктир) 2026-08-31 21:30:17 +03:00
08d1fa1af3 fix(gps): бейдж «Нет GPS» = нет валидной GPS-точки дольше N часов (настройка), убран pin от stale-пересылок 2026-08-31 17:36:40 +03:00
0ad9bb8715 fix(gps): убран слой детекции LBS по stale re-send (помечал ~100% точек); is_lbs только по протокольному флагу valid=false + физика 2026-08-31 17:02:56 +03:00
cbb659aed7 feat(gps): valid=false от протокола H02 → точка сохраняется как LBS-примерная (не выбрасывается), сигнал потери GPS 2026-08-31 15:13:03 +03:00
bec3cc054d feat(gps): траекторный фильтр трека (медианный якорь из 10 точек + lookahead-подтверждение переезда); скорость у выживших LBS-точек с «~» 2026-08-30 20:26:36 +03:00
3f4d35ae83 fix(gps): offroad-фильтр при выдаче — все точки, не только isLbs (легализованный LBS обходил фильтр и ломал OSRM match) 2026-08-29 21:49:58 +03:00
3ff74d0d17 fix(gps): zIndex точек трека над полилиниями + capture-клики; offroad-фильтр LBS при выдаче трека (чистит старый «мяч») 2026-08-29 21:36:55 +03:00
a302f0c0ea feat(gps): фильтр «далеко от дороги» в LBS-режиме (offRoadMeters, OSRM nearest); пробег «точно + примерно (по вышкам)» в Маршрутах 2026-08-29 21:12:09 +03:00
60bd52b75a feat(gps): режим «потерян GPS» — LBS-точки помечаются (is_lbs), примерная позиция на карте, пунктир в треке; OSRM map matching трека (matched endpoint + переключатель «По дорогам») 2026-08-28 17:22:57 +03:00
1af4126f7c feat(gps): индикатор потери GPS у объектов (last_gps_fix_at из stale re-send/принятых точек), настройка порога в часах 2026-08-28 10:48:32 +03:00
96e088c890 fix(gps): фильтр несогласованности скоростей против LBS-телепортов; fix: плейхед не закрывает balloon точек трека 2026-08-28 10:33:45 +03:00
ac4f89d42e feat(gps): фильтры heartbeat-эхо/valid=false/spike; доступ к GPS и Финансам — матрица на /users (пользователи+роли) 2026-08-26 17:13:49 +03:00
8f5789ec28 feat(gps): фокус геозоны на карте, город по умолчанию для всех карт, доступ к GPS по пользователям/ролям (админ) 2026-08-26 10:20:38 +03:00
3e8849019a feat(gps): скорость в км/ч (конверсия узлов Traccar + backfill), деление маршрута на поездки, проигрывание от любой точки, скорости x100-x1000 2026-08-26 09:24:29 +03:00
a4b508f70e fix(gps): гистерезис подтверждения jump-кандидата — latch-up на LBS, реальные переезды принимаются после 3 точек 2026-08-25 18:06:43 +03:00
3d24a15fb3 fix(gps): отсев устаревших копий и LBS-телепортов (stale/jump фильтры), настройка maxJumpSpeedKmh, фильтрация трека при выдаче 2026-08-25 17:49:38 +03:00
b635d1e16e feat(gps): last_seen по времени приёма, фильтр джиттера (настройки), линейка, трек по скорости; fix: карта не перекрывает диалоги (isolation) 2026-08-25 17:01:44 +03:00
bde9c2a641 feat(gps): левая панель-дерево объектов вместо select'ов; fix(csp): разрешён api-maps.yandex.ru для JS API 2.1 2026-08-25 14:59:09 +03:00
104b3ca422 feat: модуль GPS-мониторинга (карта Яндекс.Карт, Traccar, геозоны, маршруты, группы) 2026-08-24 19:30:11 +03:00
c0e2ddd9c8 Исправлено дублирование сообщений в task-чате: защита от двойного Enter/клика по кнопке отправки 2026-08-24 16:53:25 +03:00
8b713e66c0 Исправлено дублирование сообщений в task-чате: защита от двойного Enter/клика по кнопке отправки 2026-08-24 16:50:05 +03:00
a4eaf65915 Отслеживание просмотров задач: task_views, POST /api/tasks/:id/view и обновление /api/home/recent 2026-08-24 15:40:30 +03:00
e93e39383f Исправлено сохранение настроек кнопки (buttonActionType, buttonUrl, buttonFormId) в bulkSaveFormFields 2026-08-21 11:42:43 +03:00
281f8da995 fix: rate limiting по пользователям из JWT + cleanup WebPush подписок и cooldown sync-push 2026-08-20 14:17:13 +03:00
5ff2aaa023 fix: RAG embedding_queue ON CONFLICT + пересоздание finance views
- enqueueEmbedding: onConflictDoNothing без предиката не находил
  частичный уникальный индекс embedding_queue_pending_entity_idx
  (WHERE processed_at IS NULL) — каждая постановка в очередь индексации
  падала. Добавлен where: isNull(processedAt).
- finance ensureViews: CREATE OR REPLACE VIEW не умеет менять тип столбца
  (stavkands text↔varchar во внешней БД omegaprom) — заменено на
  DROP VIEW IF EXISTS + CREATE VIEW (зависимых объектов нет).
2026-08-06 15:10:38 +03:00
47869971e6 feat: объединение страниц профиля — /profile рендерит карточку пользователя
- /profile теперь обёртка MyProfilePage над UserProfilePage (один экран для своего и чужого профиля)
- self-вкладки из ProfileSettings перенесены: Уведомления, Offline, Интерфейс, Безопасность (components/profile/*)
- ProfileSettings.tsx удалён
- админ видит вкладки Уведомления и Offline на чужой карточке с данными целевого пользователя
  (userId-параметр в GET/POST /api/subscriptions, PATCH/DELETE по владельцу, GET /api/sync/config?userId=; проверка canManageUser)
- Роль/Активен/Статус disabled для не-админа (сервер их и раньше игнорировал)
- пункт меню «Настройки профиля» переименован в «Мой профиль»
2026-08-05 11:48:07 +03:00