- 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>
Aleksey Filippov [Thu, 26 Feb 2026 15:47:41 +0000 (18:47 +0300)]
fix(ERP-241): use InvalidArgumentException in TimetableService for proper error display
TimetableService threw generic \Exception which EventBehavior couldn't
handle, causing "Произошла неизвестная ошибка" in mobile app instead of
actual validation messages. Changed to InvalidArgumentException (matching
pattern used by all other api3 services) and switched from Json::encode
to firstErrors[0] for human-readable messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Filter by entity=write_offs_products_erp_video (only write-off videos, not all files)
- Add --min-age=2h param: skip files younger than 2h (queue still processing)
- Reduce default --days from 10 to 2 (cron safety net for queue failures)
- Fix PHP hanging: switch from exec/system to proc_open with /dev/null pipes
- Fix timeout: raise from 600s to 1800s for large files
- Update actionStatus: show write-off video stats only (MOV+AVI / MP4)
- Update WriteOffsErp: change border date from -2 month to -1 month
- Add WriteOffsAttachmentsController: sync border_date comment
- Add erp24/docs/diagrams/convert-video-controller.html
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Aleksey Filippov [Fri, 20 Feb 2026 13:47:30 +0000 (16:47 +0300)]
feat(BR-132): add is_promo_balance flag to promocode
Boolean field to distinguish promo codes that credit separate promo
balance vs regular bonuses. Propagated to child codes on generation
and bulk update. UI toggle added to edit form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Fri, 20 Feb 2026 13:33:10 +0000 (16:33 +0300)]
feat(BR-132): add alphanumeric promo code format PROMO-XXXX-XXXX
Add format switcher for promo code generation: legacy digits (CODE123)
and new alphanumeric (CODE-XXXX-XXXX) without confusing chars (0/O/1/l/I).
Expand code column from VARCHAR(13) to VARCHAR(20).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aleksey Filippov [Thu, 19 Feb 2026 16:50:01 +0000 (19:50 +0300)]
auto-claude: subtask-4-3 - Add Plyr JS init and CSS styles via registerCss
- Added registerCss with video-plyr-wrap wrapper styles
- Wrapped video elements in div.video-plyr-wrap for proper Plyr styling
- Plyr initialization already present from previous subtask
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Aleksey Filippov [Thu, 19 Feb 2026 16:48:41 +0000 (19:48 +0300)]
auto-claude: subtask-4-2 - Заменить блок видео на Plyr.js плеер + карточку скачивания
- Добавлен класс write-offs-video для инициализации Plyr.js плеера
- Для AVI файлов создана карточка скачивания с классом video-download-card
- MP4/MOV воспроизводятся через Plyr.js с кастомными контролами
- MOV файлы имеют дополнительную ссылку на скачивание
- Добавлена JS инициализация Plyr плееров при загрузке страницы
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Aleksey Filippov [Thu, 19 Feb 2026 16:47:21 +0000 (19:47 +0300)]
auto-claude: subtask-4-1 - Подключить Plyr.js CSS и JS через registerCssFile/registerJsFile
- Added Plyr.js CSS from CDN (https://cdn.plyr.io/3.7.8/plyr.css)
- Added Plyr.js JS from CDN (https://cdn.plyr.io/3.7.8/plyr.min.js)
- CSS registered with POS_HEAD position for proper styling
- JS registered with POS_END position for optimal page loading
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Aleksey Filippov [Thu, 19 Feb 2026 16:43:10 +0000 (19:43 +0300)]
auto-claude: subtask-2-3 - Добавить авто-конвертацию MOV/AVI → MP4 в saveUploadedFile()
- Добавлена авто-конвертация MOV/AVI файлов в MP4 после saveAs()
- При успешной конвертации оригинальный файл удаляется
- URL в записи Files обновляется на MP4 версию
- Если FFmpeg недоступен, файл сохраняется в оригинальном формате
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Aleksey Filippov [Thu, 19 Feb 2026 16:41:23 +0000 (19:41 +0300)]
auto-claude: subtask-2-2 - Реализовать метод FileService::convertToMp4()
Добавлен метод для конвертации видео MOV/AVI в MP4 через FFmpeg:
- Проверка наличия FFmpeg через `which ffmpeg`
- Проверка существования исходного файла
- Использование безопасного escapeshellarg() для shell-команд
- Флаг -y для перезаписи без подтверждения
- Флаг -movflags +faststart для быстрого старта воспроизведения
- Graceful fallback: возвращает null при ошибках без исключений
- Логирование через Yii::warning() с категорией 'video'
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>