Aleksey Filippov [Tue, 24 Mar 2026 11:49:26 +0000 (14:49 +0300)]
fix(ERP-33): TelegramTarget — экранировать \n в error_tracking.log
Многострочные ошибки (содержащие $_SERVER dump) ломали формат
файла error_tracking.log: file() разбивала одну запись на
множество строк, каждая выводилась как "Некорректная строка".
Фикс: str_replace \n → \\n при записи, убран error_log спам.
Aleksey Filippov [Tue, 17 Mar 2026 06:46:45 +0000 (09:46 +0300)]
fix(ERP-258): агрегация сообщений и дедупликация в TelegramTarget
- Обработка всех сообщений буфера вместо только первого
- Включён shouldSendMessage() для дедупликации (1 раз в день на ошибку)
- Лимит 800 символов на сообщение, 4000 итого
- disableNotification только если нет ERROR-уровня
Aleksey Filippov [Mon, 16 Mar 2026 13:17:43 +0000 (16:17 +0300)]
fix(ERP-257): graceful fallback для HEIC-файлов, которые уже являются JPEG
iPhone иногда сохраняет JPEG с расширением .HEIC. Библиотека heic2any
бросала ERR_USER вместо того, чтобы пропустить файл. Теперь при ошибке
"already browser readable" файл передаётся как есть с корректным расширением.
fix(ERP-255): защита от гонки процессов в маркетплейс-командах
- Добавлен компонент PgsqlMutex в console.php для advisory locks
- Обёрнуты 4 action MarketplaceController в мьютекс (yandex-orders,
flowwow-orders, flowwow-retry, check-ready-to-1c) — повторный запуск
cron пропускается без ошибок
- Добавлен try-catch IntegrityException в processOrders() вокруг
save() нового заказа — дубликат marketplace_order_id при гонке
теперь логируется как warning, а не падает в ошибку
- Исправлен NPE в SendBonusInfoToSiteJob: добавлена проверка на null
перед обращением к ->status++ (Fatal Error при отсутствии записи)
- Добавлен layout => false в api1.config.php
fix(ERP-254): throw 404 when no open shift found on close
TimetableFactModel::getLast() returns null if no open fact exists
for the admin on the current/previous date. Accessing checkin_start_id
on null caused PHP Warning and 500 response. Now returns proper 404.
fix(marketplace): aggregate missing MarketplaceStore errors before Telegram send
Instead of sending one Telegram message per order with missing store
inside processOrders() loop, collect unique warehouse GUIDs and send
a single aggregated message after the loop to avoid Telegram 429.
VVF [Fri, 6 Mar 2026 14:18:44 +0000 (17:18 +0300)]
fix(TO8-22): skip Yii validation on UsersBonus save in activatePromocode
Множество полей БД (dell, status, check_id и др.) не имеют дефолтов
на уровне модели. save(false) пропускает валидацию Yii — аналогично
actionSale() строка 902.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VVF [Fri, 6 Mar 2026 12:46:54 +0000 (15:46 +0300)]
fix(TO8-22): критические исправления промо-списания БЛАГО по код-ревью
Исправлены баги, выявленные при ревью:
1. Race condition: SELECT FOR UPDATE в actionActivatePromocode
2. burn_balans не трогается при промо-списании (только обычные бонусы)
3. user_balans_new не уменьшается при промо (списываются промо-бонусы)
4. writeOffAlready проверяет tip_sale='promobonus' для промо-дедупликации
5. date_end фильтр на promoPlusSum (просроченные промо не учитываются)
6. Обновление полей пользователя на промо early-return пути
7. Использование promocode.duration вместо YEAR_PERIOD в активации
8. attributeLabels для activated_by/activated_at в Promocode
9. Убран file_put_contents с промо-пути
10. Тесты обновлены: добавлены проверки баланса, исправлена формула
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
VVF [Fri, 6 Mar 2026 11:11:42 +0000 (14:11 +0300)]
feat(TO8-22): промокоды БЛАГО — активация и промо-списание бонусов
- Миграция: добавлены поля activated_by, activated_at в таблицу promocode
- Модель Promocode: константа TIP_SALE_PROMOBONUS, метод isActivatable()
- API endpoint actionActivatePromocode(): начисление 350 промо-бонусов с tip_sale='promobonus'
- Модификация actionSale(): автоматический выбор промо-списания vs стандартного, без кэшбека при промо
- Unit-тесты: 15 тестов (isActivatable + алгоритм промо-списания)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(TO8-48): increase session timeout to 4h, add JS keep-alive ping
Prevent BadRequestHttpException (Unable to verify your data submission)
in WriteOffsErpController caused by PHP session expiry during long form filling.
- erp24/config/web.php: add session component, timeout=14400 (4 hours)
- SiteController: add actionKeepAlive() endpoint for JS ping
- session-keep-alive.js: ping /site/keep-alive every 15 min, pause on hidden tab, skip during upload
- main.php: register session-keep-alive.js globally
- Fix `checkAndSetReadyTo1c()`: cast `readyto_1c` to int before strict
comparison — PostgreSQL returns string "0", so `!== 0` always skipped
- Add `forceReadyTo1cByTimeout()`: batch-marks orders older than 15 min
as ready for 1C export, independent of incoming FlowWow emails
- Add console action `marketplace/check-ready-to-1c` for cron (*/5)
- Add unit tests for both string and int readyto_1c values
fix: widen delivery text cleanup regex, cap house to 16 chars
- Change regex from matching only "Уточните адрес доставки" to
"Уточните \S+ доставки" to catch "время", "дату", etc.
- Add mb_substr guard: house field capped at 16 characters (DB limit)
- Fixes: "house should contain at most 16 characters" validation error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-252): use district as street fallback, add prod data fixtures
YM API often returns address without street but with district
(e.g., "микрорайон Красногорка"). Now uses district as street fallback.
Added 6 test cases based on real production data:
- #4497: no street, has district (Бор, микрорайон Красногорка)
- #3519: has street (улица Генерала Зимина)
- #4328: district only (микрорайон Щербинки-1)
- #4297: no street, no district (деревня Анкудиновка)
- STRING format with "Уточните время"
- NULL delivery in raw_data
26 tests, 133 assertions — all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-252): fallback when address object exists but street is empty
$delivery->getAddress() returns non-null object with empty street field.
Changed from if/else to unconditional check: if street is still empty
after structured address fill, apply parseAddressFromDeliveryText()
or "Уточняется" defaults. Markers updated to [ERP-252-v3].
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-251): parse delivery address from text when API returns no structured address
Some orders (FlowWow) have delivery info as free text in raw_data
instead of structured address fields. When $delivery->getAddress()
returns null, fallback to parseAddressFromDeliveryText() to extract
city/street/house from the text. Applied in all 3 delivery creation
points in processOrders().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-251): replace file_put_contents with Yii logging in DataBuhController
Hardcoded path /var/www/erp24/api2/json_buh does not exist on deploy
server (/var/www/erp24_deploy/), causing file_put_contents errors.
Replace debug file dumps with Yii::info/Yii::error under 'buh.upload'
category. Request data is already persisted via ApiCronBuh model.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-251): improve delivery address parsing from FlowWow emails
parseAddressFromDeliveryText() did a naive comma-split on raw
deliveryText, treating date/time fragments and instructions like
"Уточните адрес доставки у получателя" as address fields. This caused
house field to exceed the 16-char DB column limit.
Now strips delivery type prefix, date/time patterns, and non-address
phrases before splitting. Handles 1-2 part addresses gracefully
instead of requiring 3+ comma-separated parts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-251): replace file_put_contents with Yii::info() in BonusController
Replace USERS_AUTH_CALL_LOG2 file writes that caused permission denied
errors on production with standard Yii::info() logging under
'bonus.auth' category. Remove unused $LOG and $USERS_AUTH_CALL_LOG2
static properties.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-250): add null check for WriteOffsErp model in actionUpdate
$query->one() can return null when the record is deleted, sent to 1C,
or the user has no store access. Without the check, line 1076 crashes
with "Call to a member function getRelation() on null".
Now throws NotFoundHttpException (404) as documented in the PHPDoc.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(ERP-248): move $store lookup before if/else to fix undefined variable
$store was defined only inside the "new order" block (line 1252), but
referenced in 5 places inside the "existing order" else-block (lines
1692, 1735, 1770, 1828, 2017), causing "Undefined variable $store"
on production.
Moved $warehouseGuid and $store assignment before the if/else split
so both branches share the same variable scope.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 21:15:10 +0000 (00:15 +0300)]
fix(ERP-248): use marketplace_store.partner_warehouse_id as fallback for DBS orders
Add partner_warehouse_id column to marketplace_store table for storing
the physical YM warehouse ID (e.g. 206008) separate from warehouse_guid
(campaign ID 142743530). Use it as fallback when getPartnerWarehouseId()
returns null for DBS orders instead of the incorrect warehouse_guid.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 20:52:41 +0000 (23:52 +0300)]
fix(ERP-248): fallback subsidy from subsidies[] when getSubsidy() returns null
YandexMarket deprecated the item-level `subsidy` field and stopped sending
it for some order types (DBS). Instead, subsidy is now in `subsidies[]`.
When getSubsidy() returns null:
- sum amounts from getSubsidies() array
- log Yii::warning with calculated value
When getPartnerWarehouseId() returns null:
- fall back to marketplace_order.warehouse_guid (valid for DBS orders)
- only Yii::error if warehouse_guid is also null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 19:56:56 +0000 (22:56 +0300)]
fix(ERP-247): skip item save on null subsidy/partner_warehouse_id, log Yii::error
When getSubsidy() or getPartnerWarehouseId() returns null — log error with
orderId + shopSku and skip saving the item (continue) instead of writing
invalid fallback values (0 / 'N/A') to DB.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>