В процессе интеграции 1С с интернет-магазином нередко применяется оперативная выгрузка остатков в момент проведения документов движения товаров (например, реализаций или заказов покупателей). Однако при таком подходе возникает типовая проблема: если пользователь открывает ранее проведенный документ и заменяет одну позицию на другую (или просто удаляет строку), платформа выгружает актуальные остатки только по товарам, присутствующим в текущей табличной части. Позиция, которую удалили из документа, физически возвращается на остаток склада в базе 1С, но внешняя система об этом не узнает, так как товар отсутствует в текущем объекте и не попадает в алгоритм выгрузки.
Разберем, почему так происходит, рассмотрим практическое решение задачи через подписки на события без изменения типовой конфигурации, а также проанализируем архитектурные альтернативы и риски отправки данных непосредственно из транзакции проведения.
Проанализируем механику работы события ОбработкаПроведения. В этот обработчик передается ссылка или объект документа, содержащий уже обновленный состав табличной части. Данные в информационной базе еще не зафиксированы окончательно, но в оперативной памяти объекта удаленной строки уже нет.
Если алгоритм формирует список номенклатуры для выгрузки остатков простым обходом строк текущей табличной части Товары, в выборку попадут только новые и оставшиеся позиции. Чтобы сайт получил актуальные остатки и по удаленному товару, необходимо получить разницу между состоянием строк документа до начала записи и составом строк в момент проведения.
Наиболее простое и быстрое решение, не требующее изменения модулей самих документов, строится на использовании двух подписок на события и встроенной коллекции ДополнительныеСвойства. Это свойство представляет собой структуру, которая существует в контексте экземпляра объекта в памяти на протяжении всего жизненного цикла записи и проведения.
Разберем по шагам реализацию этого механизма:
ПередЗаписью для требуемых документов. В ней мы считываем из базы данных исходное состояние строк документа по его ссылке и помещаем список номенклатуры в ДополнительныеСвойства.ОбработкаПроведения (или используем существующую). В ней мы получаем текущие строки, объединяем их со сохраненным списком из ДополнительныеСвойства, удаляем дубли и рассчитываем остатки по полному перечню товаров.Посмотрим на пример реализации обработчика в подписке на событие ПередЗаписью:
Процедура ПередЗаписьюДокументаДляВыгрузки(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Проверяем, что документ уже существует в базе (не новый) и проводится
Если РежимЗаписи <> РежимЗаписиДокумента.Проведение Или Источник.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Считываем состав табличной части из базы данных по текущей ссылке
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| Т.Номенклатура КАК Номенклатура
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК Т
|ГДЕ
| Т.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Источник.Ссылка);
ТаблицаСтарыхПозиций = Запрос.Выполнить().Выгрузить();
// Сохраняем массив или список номенклатуры в дополнительных свойствах объекта
МассивСтарыхПозиций = ТаблицаСтарыхПозиций.ВыгрузитьКолонку("Номенклатура");
Источник.ДополнительныеСвойства.Вставить("ПредыдущийСоставНоменклатуры", МассивСтарыхПозиций);
КонецПроцедуры
Теперь посмотрим, как обработать сохраненные данные в событии ОбработкаПроведения:
Процедура ОбработкаПроведенияВыгрузкаОстатков(Источник, Отказ, РежимПроведения) Экспорт
Если Отказ Тогда
Возврат;
КонецЕсли;
// Формируем результирующий список номенклатуры
СписокДляВыгрузки = Новый Соответствие;
// 1. Добавляем текущие строки из документа
Для Каждого Стр Из Источник.Товары Цикл
Если ЗначениеЗаполнено(Стр.Номенклатура) Тогда
СписокДляВыгрузки.Вставить(Стр.Номенклатура, Истина);
КонецЕсли;
КонецЦикла;
// 2. Добавляем старые строки, сохраненные перед записью
Если Источник.ДополнительныеСвойства.Свойство("ПредыдущийСоставНоменклатуры") Тогда
Для Каждого Позиция Из Источник.ДополнительныеСвойства.ПредыдущийСоставНоменклатуры Цикл
СписокДляВыгрузки.Вставить(Позиция, Истина);
КонецЦикла;
КонецЕсли;
// Получаем результирующий массив уникальных позиций
МассивНоменклатуры = Новый Массив;
Для Каждого КлючИЗначение Из СписокДляВыгрузки Цикл
МассивНоменклатуры.Добавить(КлючИЗначение.Ключ);
КонецЦикла;
// Вызываем алгоритм актуализации остатков
АктуализироватьОстаткиПоСписку(МассивНоменклатуры);
КонецПроцедуры
Рассмотренный выше вариант решает локальную задачу редактирования строк, однако при эксплуатации боевой системы важно учитывать критические риски выгрузки непосредственно в момент проведения:
ОбработкаПроведения не вызывается вовсе. Товар вернется на склад, но сайт об этом не узнает.Более универсальный и отказоустойчивый способ — отслеживать не строки табличных частей разных документов, а фактические изменения в регистрах остатков (например, через подписку на событие ПередЗаписью объекта РегистрНакопленияНаборЗаписей).
Когда документ перепроводится или снимается с проведения, платформа очищает старые движения и записывает новые. В процедуре ПередЗаписью набора записей мы можем прочитать запросом текущие движения из базы данных по регистратору, сравнить их с новым набором (ЭтотОбъект) и выявить разницу. Преимущество подхода: алгоритм работает единообразно для любых документов (поступления, реализации, перемещения, инвентаризации) и автоматически фиксирует отмену проведения.
Чтобы полностью исключить риски блокировок СУБД и падений транзакций, выгрузку на сайт разделяют на два этапа:
ПланыОбмена через метод ПланыОбмена.ЗарегистрироватьИзменения(). Запись в локальную таблицу 1С выполняется за доли миллисекунды и не зависит от доступности интернета.Такая схема обеспечивает максимальную стабильность: пользователи не замечают задержек при проведении документов, а сайт всегда гарантированно получает актуальные данные даже при сетевых сбоях и массовом перепроведении документов.