Repository navigation
[Bug] Грид «Товары категории»: свои поля msProductData не читаются и молча не сохраняются #914
Description
Описание проблемы
В гриде «Товары категории» колонка, настроенная на своё поле msProductData («Свои поля»), не работает в обе стороны:
- значение не читается — колонка пустая, хотя у товаров поле заполнено;
- значение не сохраняется — правка через inline-редактирование теряется, причём API отвечает
{"ok": true}, то есть интерфейс показывает успешное сохранение.
Вторая часть опаснее первой: данные теряются молча, без ошибки.
Проблема шире своих полей — тем же задето штатное поле stock, см. раздел «Дополнительный контекст».
Источник: сообщение пользователя на modx.pro.
Шаги воспроизведения
- Создать своё поле для
msProductDataчерез «Утилиты → Свои поля», напримерwholesale_price(numberfield,decimal). - Заполнить это поле у какого-нибудь товара (в карточке товара поле сохраняется нормально).
- Открыть «Утилиты → Конфигурация гридов», грид «Товары категории», добавить колонку с этим полем и отметить её как редактируемую.
- Открыть категорию с этим товаром.
- Посмотреть на колонку, затем отредактировать её прямо в гриде.
Ожидаемое поведение
Колонка показывает значение поля из базы. Правка через грид сохраняется — так же, как это уже работает в карточке товара.
Фактическое поведение
Колонка пустая. Правка не доезжает до базы, но интерфейс сообщает об успехе.
Замеры на товаре 152351, поле wholesale_price, значение в базе 7.00:
значение в таблице ms3_products: '7.00'
через модель ($product->loadData()): 7.0 — поле есть в карте
getDataFieldsNames() содержит поле: ДА
ключ 'wholesale_price' в строке листинга: ОТСУТСТВУЕТ
updateProductData(152351, ['wholesale_price' => 777.77])
ответ: {"ok":true, "data":{... "wholesale_price":7 ...}}
значение в базе после записи: '7.00' — не изменилось
контроль, штатное поле article: сохранилось
Строка листинга содержит только фиксированный набор ключей:
id, pagetitle, longtitle, alias, parent, menuindex, published, deleted,
hidemenu, createdon, editedon, article, price, old_price, weight, image,
thumb, vendor_id, made_in, new, popular, favorite, preview_url
Причина
Поля проходят через три статических списка, ни один из которых не знает про «Свои поля»:
| Место | Что делает |
|---|---|
Services/Category/CategoryProductsListService.php:70 |
$selectParts — фиксированный перечень Data.* в SELECT: своё поле не выбирается из базы |
Services/Category/CategoryProductsListService.php:347 |
formatProductRow() — собирает строку ответа из фиксированного набора ключей |
Services/Product/ProductDataService.php:27 |
$allowedUpdateFields — белый список записи; несовпавшие ключи отбрасываются через array_intersect_key без сообщения |
Опции и поля связей попадают в грид через отдельные механизмы (GridOptionColumnResolver, GridRelationColumnResolver), а колонки собственных полей модели не поддержаны ни одним из них.
При этом карточка товара с теми же полями работает: Processors/Product/ProductDataPayloadTrait.php:118 строит список динамически — getDataFieldsNames() (вся карта модели вместе со своими полями) минус id и preview_file_id.
Конфигурация гридов добавить такую колонку и отметить её редактируемой позволяет, то есть интерфейс обещает возможность, которой в бэкенде нет.
Дополнительный контекст
Задето и штатное поле stock. Списки чтения и записи расходятся между собой: stock входит в $allowedUpdateFields, но отсутствует и в $selectParts, и в formatProductRow(). Проверено на том же товаре:
stock в базе: 25
stock в строке листинга: ОТСУТСТВУЕТ
updateProductData(152351, ['stock' => 99]) → ok: true
stock в базе после записи: '99.000' — записался
То есть колонка «Остаток» в этом гриде пустая, но правка в неё уходит в базу: значение пропадает с экрана, хотя сохранилось.
Предлагаемое направление. Заменить статические списки на динамический по образцу ProductDataPayloadTrait: брать колонки, настроенные в гриде, пересекать с getDataFieldsNames() и исключать служебные (id, preview_file_id). Это закрывает и расхождение по stock. Белый список записи при этом должен остаться — именно он защищает служебные колонки, просто строиться ему нужно от карты модели, а не от зашитого перечня.
Не регрессия 1.14.2-beta1. Проверялось отдельно, поскольку в этом релизе менялся CategoryProductsController (#473): обе ветки листинга — быстрая и с проверкой ACL — вызывают один и тот же getPage(), существовавший до той правки, и набор полей она не затрагивала.
Окружение
- MiniShop3: 1.14.2-beta1
- MODX: 3
- PHP: 8.3.25
- MySQL: не влияет
- Браузер: не влияет
Activity
fix(mgr-api): свои поля msProductData и stock в гриде «Товары категор…
chore(release): 1.14.3-beta1 (#941)