Некорректный рефреш страницы с заказами Принято

3

Эта тема уже поднималась здесь: https://support.webasyst.ru/fo... , по традиции ей присвоена плашка «разберёмся когда-нибудь (т.е. никогда» («Принято» если кратко).

Штош. Будем жечь свои токены, раз такое дело. Ничего для вебассиста не жалко. Вот тут Opus всё исследовал.

==========================================


Это баг ядра Shop-Script, не плагина — воспроизвёл и подтвердил стек-трейсом.

Механизм

  1. shopOrders.action.php:303 — getLastUpdateDatetime() возвращает max(array_column($orders,'update_datetime')), то есть ровно timestamp самого свежего заказа в списке.
  2. Это значение уходит в JS как options.update_process.last_update_datetime, и list.js:1528 updateProcess() каждые 60 сек (orders_update_list) дёргает loadList.
  3. Первый тик спрашивает search=update_datetime >=<то же самое значение> — сравнение включающее (list.js:1524). Сервер честно возвращает тот же самый заказ как «обновлённый», хотя ничего не менялось.
  4. order_update() → updateOrderList(isSelected) → list.js:1683 self.loadOrder(order.id) — панель заказа перерисовывается, и любая открытая в ней форма уничтожается.
  5. Дальше last_update_datetime сдвигается на +60 сек, и до следующего рефреша баг не повторяется — ровно как вы и описали.

Поэтому это бьёт и по штатной «Изменить параметры доставки»: форма живёт внутри той же панели.

Доказательство (натуральный прогон, без подкруток)
initial last_update_datetime = 2026-08-17  18:10:41 (= update_datetime заказа 13)
sec 42  search=update_datetime>=2026-08-17 18:10:41 &rarr; upd: ["13@2026-08-17 18:10:41"]
sec 42  list.loadOrder(13) &rarr; форма &laquo;Изменить параметры доставки&raquo; уничтожена
sec 45  (следующий тик) &rarr; upd: [] больше не повторяется

Предусловие: выбранный заказ должен быть тем, у кого max(update_datetime) среди загруженных. Сортировка по умолчанию — create_datetime (shopOrderListAction.class.php:386), так что формально не гарантировано, но в реальном магазине свежий заказ обычно и есть последний изменённый — отсюда «всегда воспроизводится».

Как блокировать

Проверил на той же странице: сдвиг границы на +1 секунду до первого тика полностью убирает эффект, опрос при этом продолжает работать (upd: [], loadOrder не вызывается, форма жива на 72-й секунде):
$.order_list.options.update_process.last_update_datetime = max + 1s

Это объект по ссылке, который читает замыкание опроса, так что мутация до первого тика работает. Оговорка: это митигация, а не гарантия — если форму открыть ровно на 60-й секунде, правка может не успеть.

Грубая альтернатива — $.order_list.updateProcess('kill') при открытии формы, но она глушит заодно счётчики и подтягивание новых заказов.

Для отчёта разработчикам

Однострочный фикс, любой из двух:
  • shopOrders.action.php:303 — возвращать max(...) + 1 секунда;
  • list.js:1524 — использовать > вместо >=.
Ключевое, что стоит вынести в баг-репорт: 

list.js:1683 перезагружает выбранный заказ и сносит любую открытую в панели форму, включая штатную «Изменить параметры доставки».

Заодно рядом: list.js:1531 timeout = options.timeout || 60000; объявлена без var — утечка в глобальную область.

3 комментария

  • +1
    Syrnik.com Syrnik.com 17 августа 2026 19:13 #

    Я правильно понимаю, что если, пока какая-либо форма открыта, заказ как-то изменится (или добавится новый), то форма всё равно схлопнется?

    ===========================

    Да — если изменится сам открытый заказ. 

    Проверил, изолировав от бага границы (сначала применил фикс +1 сек, потом поменял update_datetime заказа
    снаружи, пока форма открыта):
    sec 46 search=update_datetime>=2026-08-06 13:33:33 → upd: ["13@2026-08-17 18:56:49"]
    sec 46 loadOrder(13) → форма схлопнулась
    

    Это уже не баг, а штатное поведение: list.js:1683 намеренно перезагружает панель, когда выбранный заказ изменился. Считается любой bump update_datetime — смена статуса коллегой, коллбэк платёжки, крон, правка из соседней вкладки.

    Важное следствие: фикс off-by-one убирает только ложное схлопывание раз в рефреш. От реального изменения заказа он не защищает — это отдельная проблема UX, и её стоит упомянуть в отчёте разработчикам отдельным пунктом.

    Нет — если добавится новый заказ

    Ветка if (r.data.count) (list.js:1570-1606) только делает self.container.prepend(...) — дописывает строки в
    список слева. Панель заказа не трогается, loadOrder не вызывается. Форма выживает. Счётчики (updateCounters) тоже панель не трогают.

    Нет — если изменится другой заказ и останется в текущем фильтре

    isSelectedOrder = false → updateOrderList(false) → перерисовывается только его строка в списке, без loadOrder.

    Да, и с сюрпризом — если другой заказ сменит статус и выпадет из фильтра

    Тут неприятная ветка в shouldUpdateOrderList() (list.js:1687-1702):

    const nextOrder = orderElem.next();
    if (nextOrder.length) {
        self.loadOrder(nextOrder.data('order-id')); // грузим СОСЕДНИЙ заказ
    }
    orderElem.remove();
    

    Она срабатывает для любого обновлённого заказа, не только выбранного. То есть чужой заказ ушёл из фильтра — а панель переключается на его соседа по списку. Форма не просто схлопывается, вы ещё и оказываетесь на другом заказе. Это, на мой взгляд, самый грубый из трёх дефектов и его тоже стоит включить в баг-репорт.
    ---
    Практический вывод: правка границы лечит только гарантированное схлопывание раз в рефреш (самый частый и самый раздражающий случай). Чтобы форма была защищена по-настоящему, ядру нужно не перезагружать панель, когда в ней открыта несохранённая форма — например, через флаг «панель занята», который опрос уважает. Локально из плагина это достижимо только глушением опроса (updateProcess('kill')) на время открытой формы, со всеми издержками.


  • +1
    Syrnik.com Syrnik.com 17 августа 2026 19:17 #

    А вот такое поведение является вариацией бага с last_update или это совсем другое? "При запросе вида /webasyst/shop/?action=orders#/orders/search/id=25 если выдается не новый заказ, через минуту список обновляется, и в нем помимо запрошенного появляются и другие заказы. Если заказ новый, этого не происходит. Проблема воспроизводится как в режиме сплита, так и в табличном представлении."

    =============

    Это родственный, но отдельный дефект. Общий у них тот же опрос и та же «точка отсчёта», а вот ломается другое. Воспроизвёл оба ваших случая.

    Поиск id=7 (старый заказ)
    после загрузки: rows = [7], last_update_datetime = 2026-05-03 21:11:58
    тик sec 51: count:3 orders:[13,12,11] updated_orders:[13,12,11,7]
    после тика: rows = [13, 12, 11, 7]
    

    Поиск id=13 (самый свежеобновлённый заказ)

    после загрузки: rows = [13], last_update_datetime = 2026-08-06 13:33:32
    тик sec 53: count:0 orders:[] updated_orders:[13]
    после тика: rows = [13]
    

    Что происходит

    Складываются два фактора:

    1. 1. getLastUpdateDatetime() берёт максимум по тем заказам, что сейчас в списке. Поиск сузил список до одного старого заказа — и точка отсчёта стала «май 2026» вместо «сейчас». Это тот же самый корень, что и в первом баге.
    2. Опрос не применяет активный фильтр поиска. Это видно однозначно: если бы id=7 учитывался, в updated_orders был бы только 7. А вернулись 13, 12, 11. Всё, что обновлялось после точки отсчёта, летит в список.
    Отсюда и ваше «если заказ новый, этого не происходит»: когда найденный заказ — самый свежеобновлённый в магазине, точка отсчёта равна максимуму по всей базе, подмешивать просто нечего. Дело не в статусе «НОВЫЙ», а в том, что заказ последний по update_datetime.

    Почему не зависит от вида — код опроса общий для split и таблицы, отдельная ветка есть только у kanban.

    Где именно

    Похоже, виновата ветка list.js:1503-1509:

    if (order_update_datetime && this.filter_params_str && this.filter_params_str.includes('hash')) {
        // ... подмешать update_datetime>= внутрь существующего фильтра
        order_update_datetime = null;
    }
    

    Она существует ровно для того, чтобы слить условие по дате с уже действующим фильтром, а не затереть его. Но срабатывает только если в filter_params_str есть подстрока hash. При поиске id=7 там лежит updated_orders=id=7 — подстроки hash нет, слияние пропускается, и &search=update_datetime>=… уходит

    вместо пользовательского условия. Эвристика «ищем подстроку hash» и есть узкое место.
    Оговорка: последний абзац — вывод из чтения кода, эмпирически я подтвердил только сам факт, что фильтр не применяется. Точную семантику updated_orders=на стороне бэкенда я не проверял.

    Плюс здесь же вылезает уже известный off-by-one: заказ 7 попал в updated_orders сам по себе (>= от собственного timestamp) — то есть на поиске к подмешиванию чужих заказов добавляется ещё и перезагрузка панели найденного.

    Итого в баг-репорт добавляется четвёртый пункт, и он, пожалуй, самый заметный для пользователя: результат поиска молча превращается в другой список через минуту после запроса.
  • +2
    Михаил Ушенин Михаил Ушенин 21 августа 2026 08:58 #

    Спасибо за подробный разбор проблемы! Всё передал разработчикам для изучения и устранения неудобств в работе с приложением.

    Добавить комментарий

    Чтобы добавить комментарий, зарегистрируйтесь или войдите