fomichev [Fri, 5 Jun 2026 07:48:09 +0000 (10:48 +0300)]
refactor: заменить Yii::$app->get(StoreService) на new StoreService() в StorePlanService
Убрана лишняя регистрация StoreService в container.singletons (web.php),
так как сервис теперь создаётся напрямую, как принято в остальном проекте.
Дублирующий запрос к CityStore заменён вызовом StoreService::getActiveStoreIds().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix: ошибки из логов — stock-state алерты в Telegram, парсинг Flowwow, PK у Images
1. stock-state: алерты слались через SendTelegramMessageJob с ключом message
→ 'unknown property', алерт не уходил. Джоба для клиентских рассылок
(нужен chat_id/phone). Переведено на TelegramService::sendErrorToTelegramMessage
(служебный канал, с таймаутами). [StockStateService::sendTelegram]
2. MarketplaceService: незащищённый доступ $statuses['CANCELLED']/['DELIVERED']
давал 'Undefined array key' при разборе писем Flowwow → добавлен ?? null
(строки 1524, 2595, 2596, единообразно с остальными местами).
3. records/Images: в таблице images нет PK-констрейнта в БД → findOne()
падал с InvalidConfigException. Добавлен явный primaryKey() => ['id'].
fix: таймауты HTTP-запросов к Telegram API (устранение 504 на dev)
Запросы к api.telegram.org выполнялись через Guzzle/cURL без таймаутов.
При недоступности api.telegram.org (резолвится только в IPv6, маршрута
нет) cURL виснет ~130с. Отправка идёт синхронно внутри обработки запроса
(в т.ч. из лог-таргета на каждый Yii::error), поэтому воркер PHP-FPM
блокировался, пул (max_children=5) исчерпывался и весь сайт отдавал 504.
Добавлены connect_timeout=2с и timeout=5с ко всем вызовам Telegram API
через единый клиент getHttpClient(), а также CURLOPT_CONNECTTIMEOUT/
CURLOPT_TIMEOUT в cURL-вызовах sendPromoMessageToTelegramDocument.
fix: таймауты HTTP-запросов к Telegram API (устранение 504 на dev)
Запросы к api.telegram.org выполнялись через Guzzle/cURL без таймаутов.
При недоступности api.telegram.org (резолвится только в IPv6, маршрута
нет) cURL виснет ~130с. Отправка идёт синхронно внутри обработки запроса
(в т.ч. из лог-таргета на каждый Yii::error), поэтому воркер PHP-FPM
блокировался, пул (max_children=5) исчерпывался и весь сайт отдавал 504.
Добавлены connect_timeout=2с и timeout=5с ко всем вызовам Telegram API
через единый клиент getHttpClient(), а также CURLOPT_CONNECTTIMEOUT/
CURLOPT_TIMEOUT в cURL-вызовах sendPromoMessageToTelegramDocument.
fomichev [Thu, 4 Jun 2026 13:29:35 +0000 (16:29 +0300)]
fix: убран двойной писатель store_dynamic cat 4 из контроллера
Контроллер больше не пишет запись CATEGORY_IS_ACTIVE в store_dynamic
при сохранении через UI — историю с корректной вчерашней датой
пишет только крон store/sync-store-dynamic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Thu, 4 Jun 2026 09:39:58 +0000 (12:39 +0300)]
ERP-396: теги каналов перенесены в city_store_params + крон синхронизации StoreDynamic
- Добавлено поле assortment_label_ids (varchar 255) в city_store_params
- Контроллер читает/пишет теги из city_store_params вместо store_dynamic
- JS: label_ids → assortment_label_ids в POST-запросе
- StoreDynamicSyncService: крон сравнивает is_active (cat 4) и assortment_label_ids (cat 5)
с активной записью store_dynamic, при изменении закрывает старую (позавчера 23:59:59)
и создаёт новую (вчера 00:00:00) — запуск 0 3 * * *
- StoreController: php yii store/sync-store-dynamic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Wed, 3 Jun 2026 10:30:01 +0000 (13:30 +0300)]
fix: upload фото — нативный GD вместо Imagine для thumbnail
Imagine/GD без libjpeg не может сохранять JPEG.
Используем imagecreatefromjpeg/png/webp + imagescale + imagejpeg
(как в FileService::uploadAvatar). Fallback: если GD не открыл файл —
превью = оригинал.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Wed, 3 Jun 2026 10:23:46 +0000 (13:23 +0300)]
ERP-396: оборудование магазина + фото в city_store_params/city_store
Оборудование (camera_model, camera_count, microphone_model, router_model,
internet_provider, cash_register_info):
- Миграция с идемпотентными проверками
- CityStoreParams: @property + правила валидации
- Controller: возврат в getStore, сохранение в saveCityStoreParams
- Карточка: read-only тайлы вместо stub-заглушек
- Операционное: редактируемые поля с сабтитлом «Техника»
Фото магазина (city_store.image_sm / image_big):
- actionUploadStorePhoto: resize 300×200 thumbnail через ImageHelper
- Hero: photo-area с превью, кнопкой камеры и click-to-enlarge
- Модал просмотра большой фотографии
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Wed, 3 Jun 2026 09:43:40 +0000 (12:43 +0300)]
ERP-397: фикс пустых дропдаунов руководства и актуализация комбо после сохранения
- Контроллер: дропдауны КШФ и тер.управляющего всегда включают текущего
назначенного сотрудника (orWhere id), даже если его group_id изменился
- JS: после сохранения перезагружаем STORES с сервера вместо ручного патча,
чтобы комбо-список гарантированно отражал актуальный статус из БД
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Wed, 3 Jun 2026 09:34:10 +0000 (12:34 +0300)]
ERP-397: синхронизация is_active → visible и обновление UI без перезагрузки
- CityStoreManagementController: при сохранении is_active обновляет city_store.visible
через CityStore::updateAll в той же транзакции
- JS: после сохранения патчит STORES[si].isActive и сразу перерисовывает комбо-список,
чтобы фильтр «Неактивен» отражал изменение без перезагрузки страницы
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Wed, 3 Jun 2026 06:50:03 +0000 (09:50 +0300)]
ERP-396: убрать индексы из миграций store_type/city_store_params, добавить идемпотентность
Таблицы небольшие — индексы не дают выигрыша.
Проверка существования колонки в safeUp/safeDown предотвращает
падение при повторных накатах и откатах миграций.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Tue, 2 Jun 2026 14:53:16 +0000 (17:53 +0300)]
ERP-389: блок Каналы (ассортиментные лейблы) в управлении магазином
- StoreDynamic: константа CATEGORY_ASSORTMENT_LABELS = 5
- CityStoreManagementController: загрузка/сохранение лейблов через
StoreDynamic category 5 (value_string = "1,3,7"); recordStoreDynamicStr();
save(true, $validateFields) вместо полной валидации — фикс ошибки
required для null SEO-полей при сохранении с Операционного таба
- JS: панель Каналы в renderOps(), теги в renderHero(), модал выбора,
openCombo() показывает полный список без фильтра по текущему тексту
- CSS: стили lbl-tag, tag-add, btn-panel-link, lbl-modal-*, hero-labels
- index.php: модальное окно + URL assortmentLabels
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fomichev [Tue, 2 Jun 2026 13:22:44 +0000 (16:22 +0300)]
ERP-389: is_active в city_store_params + новый интерфейс управления магазином
- Разделены поля visible (CityStore) и is_active (CityStoreParams):
visible больше не меняется при смене is_active
- Неактивные магазины остаются в списке /city-store-params с оранжевым фоном
- Добавлены константы категорий в StoreDynamic (CATEGORY_IS_ACTIVE=4 и др.)
- История активности: изменение is_active записывается в store_dynamic (category=4)
- Новый интерфейс /city-store-management на основе мокапа v8:
4 вкладки (Карточка, Операционное, SEO, Сервисная), комбобокс выбора магазина,
AJAX-сохранение, dirty-tracking, RBAC на вкладку Сервисная (Директор + IT)
- Миграции: store_type.code (slug) и city_store_params.is_active
- StoreService зарегистрирован как singleton в DI-контейнере
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deploy: рестарт yii-queue воркеров после reload php-fpm
Воркеры yii queue/listen читают .env один раз при старте и кэшируют
vendor/autoload в памяти. Без принудительного рестарта они продолжают
работать с прежними токенами/кодом задач — и любое изменение в .env
или коде джоб до них не доедет.
Инцидент 2026-05-07: после ротации TELEGRAM_BOT_TOKEN_PROD в @BotFather
воркеры с uptime 68 дней (last restart Feb27) продолжали слать с
отозванным токеном, выдавая 401 Unauthorized — ERP молча терял все
уведомления клиентам с 21.04 по 07.05.
Дополнительно убиваются orphan-процессы yii queue/listen с PPID=1:
такие появляются, если supervisor когда-то рестартовали без graceful
stop детей — supervisorctl restart их НЕ трогает, а они продолжают
слушать очередь со старым окружением. На erpp 2026-05-07 жили ровно
10 таких orphan'ов от Feb27 параллельно с новыми воркерами.
fix(ERP-372): синхронизировать фильтры getSalesCountSum с getSalesSum
На дашборде /dashboard/sales для магазинов с большой долей доставки
(например, 07 Аэродромная 28) средний чек расходился с суммой продаж:
avg = sum_count_query / cnt_count_query, при этом колонка «сумма продаж»
показывала результат другого запроса (getSalesSum) с более узким
фильтром.
Привожу getSalesCountSum к тем же условиям:
- LEFT JOIN sales sc ON sc.id = s.sales_check
- sc.order_id IS NULL OR IN ('','0') — отбрасываем строки, привязанные
через sales_check к доставочной продаже;
- s.marketplace_order_id IS NULL OR ='' — отбрасываем продажи маркетплейсов.
После фикса avg = sum / cnt совпадает с тем, что выводится в колонке
«сумма продаж».
Тесты (9/9 GREEN): unit на структуру SQL и биндинг параметров +
интеграционные на реальной БД (insert/rollback в транзакции),
проверяющие исключение маркетплейс- и доставочно-связанных продаж.
test(ERP-372): добавить тесты для getSalesCountSum
Unit-тесты на SQL-структуру, биндинг параметров, payType-ветку
и пустой результат. Интеграционный тест на реальной БД проверяет,
что метод исключает маркетплейс-продажи и записи, связанные через
sales_check с доставкой. На текущем (нефиксенном) коде воспроизводят
баг — RED-состояние перед применением правки.
fix(task_22): расширить окно выборки админов с 1 до 4 месяцев
Сотрудники с пустым guid выпадали из синхронизации с 1С после месяца —
расширяем окно date_add до 4 месяцев. Добавлена документация по каналу
ERP→1С create_employee (pull через API2 DataController::actionRequest).