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>
Aleksey Filippov [Sat, 28 Feb 2026 19:35:06 +0000 (22:35 +0300)]
fix(ERP-247): add warning logs for null subsidy/partner_warehouse_id, restore delivery info as Yii::info
- Extract getSubsidy() and getPartnerWarehouseId() to local vars in all 6 item-saving blocks
- Log Yii::warning with orderId + shopSku when either returns null
- Restore срок доставки shipments loop (was removed as debug, actually informational)
- Change level from Yii::error to Yii::info for shipment delivery time logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 18:57:39 +0000 (21:57 +0300)]
fix(ERP-247): improve delivery save error logging in MarketplaceService
- Log deliveryText when type not recognized (was silent return false)
- Log delivery model errors + deliveryText when model.save() fails
(previously logged wrong model's errors - marketplaceOrder instead)
- Fix callers to log order id instead of empty marketplaceOrder.getErrors()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 17:23:29 +0000 (20:23 +0300)]
fix(ERP-247): add CSRF token to AJAX calls in WriteOffsErp view
$.ajax() calls for confirm-write-off and re-send-write-off were posting
without _csrf token, causing BadRequestHttpException 400 on every request.
Added yii.getCsrfToken() to data payload of both calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 15:52:59 +0000 (18:52 +0300)]
chore: update .claude/skills — refactor to <500 lines + references/assets
- Extracted detailed content from 21 skills into references/ and assets/
- Each SKILL.md now acts as a hub (<500 lines) with JiT-loaded references
- Added new skills: code-review-expert
- Added references/ dirs for: jira-*, github-*, swarm-*, sparc-*, pair-programming, etc.
- Added assets/ dirs for: agentdb-optimization, flow-nexus-platform, github-*, etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 12:02:39 +0000 (15:02 +0300)]
fix(ERP-246): replace hardcoded OUT_DIR with relative path in UploadService
Hardcoded path /var/www/erp24/api2/json caused Permission denied errors
on production when log files were owned by non-www-data users.
Using __DIR__ . '/../api2/json' makes the path deployment-agnostic.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 12:01:27 +0000 (15:01 +0300)]
fix(ERP-246): add missing api1/views/site/index.php to fix ViewNotFoundException
api1 and main app share namespace app\controllers, so SiteController
from the main app is accessible via api1. When GET / hits api1, Yii2
routes to SiteController::actionIndex() which calls render('index'),
but api1/views/site/index.php did not exist → ViewNotFoundException (500).
Added empty view file to stop the error. api1 is a REST API (FORMAT_JSON)
so the rendered empty string is JSON-encoded as "".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Sat, 28 Feb 2026 08:17:53 +0000 (11:17 +0300)]
fix(ERP-245): separate broken-file marker from image src URL
- WriteOffsErp::getImagesList() previously set $imageThumbRow='broken_file-error'
as sentinel, then built src="/broken_file-error" causing Yii2 to try routing
it as a controller action → repeated 404 errors in production logs
- Separated marker (kept in $relaFileName for alt/debug) from URL (now points
to valid static placeholder /images/no-image.svg)
- Added erp24/web/images/no-image.svg: lightweight SVG fallback served by nginx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Fri, 27 Feb 2026 21:38:15 +0000 (00:38 +0300)]
fix(ERP-245): prevent TypeError when Domru XML has single count element
When XML response contains only one count element, json_decode(json_encode($xml))
produces a flat array instead of nested, causing $arr to be a string.
Skip non-array entries to avoid "Cannot access offset of type string on string".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Fri, 27 Feb 2026 15:06:41 +0000 (18:06 +0300)]
fix(ERP-244): fix CSRF validation error in ShiftReminderController
- Disable CSRF validation on ShiftReminderController: endpoint is
protected by session authentication (AccessControl, roles=['@']).
CSRF cookies may be absent in browsers with strict privacy settings,
causing false 400 errors for legitimate authenticated users.
- Stop retrying on 400/401/403 responses in shift-reminder.js to prevent
cascading error floods in logs when auth/validation fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Thu, 26 Feb 2026 19:58:14 +0000 (22:58 +0300)]
fix(ERP-243): catch all exceptions in TimetableService transaction rollback
catch (Exception) only caught yii\db\Exception, missing
InvalidArgumentException from validation/upload errors. This left
orphaned timetable records with tabel=1 but no timetable_fact entry,
blocking shift reopening.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Thu, 26 Feb 2026 18:19:45 +0000 (21:19 +0300)]
fix(ERP-242): validate XML response from Domru API before parsing
- Skip iteration on empty curl response instead of writing empty file
- Use libxml_use_internal_errors to catch invalid XML gracefully
- Auto-create log/text/ directory if missing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Thu, 26 Feb 2026 16:43:48 +0000 (19:43 +0300)]
fix: correct bubble.png path in minified CSS
Minified CSS in web/min/ referenced ../images/png/bubble.png which
resolved to a non-existent web/images/png/ directory, causing 404.
Fixed to ../azea/assets/images/png/bubble.png where the file actually exists.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>