diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce2f8f1..5b35d2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,14 @@
---
+## 1.3.1.102 (2026-08-31)
+
+### РК_ЗагрузкаОстатков — свернуть комиссионные остатки (#19, crm3-migration#24)
+
+- Команда **«Свернуть комиссионные остатки»** на форме и на вкладке комиссии.
+- Алгоритм: пары «−qty без серии / +qty с серией» при равных |qty|; затем группы с net qty ≈ 0.
+- Экспорт `СвернутьКомиссионныеОстатки()` в ObjectModule.
+
## 1.3.1.101 (2026-08-21)
### РК_ПроверкаСерий — отображение пометки удаления дублей (#15)
diff --git a/VERSION b/VERSION
index 4fbbd80..c085ada 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.3.1.101
+1.3.1.102
diff --git a/src/Configuration.xml b/src/Configuration.xml
index caa86e8..a2a73c0 100644
--- a/src/Configuration.xml
+++ b/src/Configuration.xml
@@ -62,7 +62,7 @@
Role.РК_ОсновнаяРоль
- 1.3.1.101
+ 1.3.1.102
Language.Русский
diff --git a/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl b/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
index 6ca20c1..6d6f8aa 100644
--- a/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
+++ b/src/DataProcessors/РК_ЗагрузкаОстатков/Ext/ObjectModule.bsl
@@ -271,6 +271,110 @@
КонецФункции
+// Схлопывает строки ТЧ ВходящиеКомиссионныеТовары:
+// 1) отрицательная без серии + положительная с серией (|qty| равны) — обе удаляются;
+// 2) оставшиеся группы с net qty ≈ 0 — все строки группы удаляются.
+//
+// Возвращаемое значение:
+// Структура - БылоСтрок, СталоСтрок, УдаленоСтрок, УдаленоПар, УдаленоГруппNetZero
+//
+Функция СвернутьКомиссионныеОстатки() Экспорт
+
+ Результат = Новый Структура;
+ Результат.Вставить("БылоСтрок", ВходящиеКомиссионныеТовары.Количество());
+ Результат.Вставить("СталоСтрок", 0);
+ Результат.Вставить("УдаленоСтрок", 0);
+ Результат.Вставить("УдаленоПар", 0);
+ Результат.Вставить("УдаленоГруппNetZero", 0);
+
+ Если Результат.БылоСтрок = 0 Тогда
+ Возврат Результат;
+ КонецЕсли;
+
+ Удаляемые = Новый Соответствие;
+
+ Группы = Новый Соответствие;
+ Для Каждого СтрТ Из ВходящиеКомиссионныеТовары Цикл
+ Ключ = КлючАналитикиКомиссионнойСтроки(СтрТ);
+ СтрокиГруппы = Группы.Получить(Ключ);
+ Если СтрокиГруппы = Неопределено Тогда
+ СтрокиГруппы = Новый Массив;
+ Группы.Вставить(Ключ, СтрокиГруппы);
+ КонецЕсли;
+ СтрокиГруппы.Добавить(СтрТ);
+ КонецЦикла;
+
+ Для Каждого ЭлементГруппы Из Группы Цикл
+ СтрокиГруппы = ЭлементГруппы.Значение;
+ Помеченные = Новый Соответствие;
+
+ Отрицательные = Новый Массив;
+ ПоложительныеССерией = Новый Массив;
+ Для Каждого СтрТ Из СтрокиГруппы Цикл
+ Если Помеченные.Получить(СтрТ) <> Неопределено Тогда
+ Продолжить;
+ КонецЕсли;
+ Кол = СтрТ.Количество;
+ Если Кол < 0 И Не СерияЗаполненаВСтрокеКомиссии(СтрТ) Тогда
+ Отрицательные.Добавить(СтрТ);
+ ИначеЕсли Кол > 0 И СерияЗаполненаВСтрокеКомиссии(СтрТ) Тогда
+ ПоложительныеССерией.Добавить(СтрТ);
+ КонецЕсли;
+ КонецЦикла;
+
+ Для Каждого НегСтр Из Отрицательные Цикл
+ Если Помеченные.Получить(НегСтр) <> Неопределено Тогда
+ Продолжить;
+ КонецЕсли;
+ НужноеКол = -НегСтр.Количество;
+ Для Каждого ПозСтр Из ПоложительныеССерией Цикл
+ Если Помеченные.Получить(ПозСтр) <> Неопределено Тогда
+ Продолжить;
+ КонецЕсли;
+ Если ПозСтр.Количество = НужноеКол Тогда
+ Помеченные.Вставить(НегСтр, Истина);
+ Помеченные.Вставить(ПозСтр, Истина);
+ Результат.УдаленоПар = Результат.УдаленоПар + 1;
+ Прервать;
+ КонецЕсли;
+ КонецЦикла;
+ КонецЦикла;
+
+ Остаток = Новый Массив;
+ СуммаКол = 0;
+ Для Каждого СтрТ Из СтрокиГруппы Цикл
+ Если Помеченные.Получить(СтрТ) <> Неопределено Тогда
+ Продолжить;
+ КонецЕсли;
+ Остаток.Добавить(СтрТ);
+ СуммаКол = СуммаКол + СтрТ.Количество;
+ КонецЦикла;
+
+ Если Остаток.Количество() > 1 И Abs(СуммаКол) < 0.000001 Тогда
+ Для Каждого СтрТ Из Остаток Цикл
+ Помеченные.Вставить(СтрТ, Истина);
+ КонецЦикла;
+ Результат.УдаленоГруппNetZero = Результат.УдаленоГруппNetZero + 1;
+ КонецЕсли;
+
+ Для Каждого ЭлементПометки Из Помеченные Цикл
+ Удаляемые.Вставить(ЭлементПометки.Ключ, Истина);
+ КонецЦикла;
+ КонецЦикла;
+
+ Для Инд = ВходящиеКомиссионныеТовары.Количество() - 1 По 0 Цикл
+ СтрТ = ВходящиеКомиссионныеТовары[Инд];
+ Если Удаляемые.Получить(СтрТ) <> Неопределено Тогда
+ ВходящиеКомиссионныеТовары.Удалить(Инд);
+ КонецЕсли;
+ КонецЦикла;
+
+ Результат.УдаленоСтрок = Удаляемые.Количество();
+ Результат.СталоСтрок = ВходящиеКомиссионныеТовары.Количество();
+ Возврат Результат;
+
+КонецФункции
+
// Создание документов ВводОстатковТоваров по сопоставленным строкам.
// Собственные — 1 документ на склад; комиссия 2.5 — 1 документ на соглашение+партнёра (без склада).
// Количество в документе — финансовое (rh_), если не 0, иначе складское.
@@ -1177,6 +1281,38 @@
КонецФункции
+Функция КлючАналитикиКомиссионнойСтроки(СтрТ)
+
+ Возврат Строка(СтрТ.НоменклатураИдентификатор) + "|"
+ + СокрЛП(СтрТ.НоменклатураКод) + "|"
+ + СокрЛП(СтрТ.Артикул) + "|"
+ + Строка(СтрТ.СоглашениеИдентификатор) + "|"
+ + СокрЛП(СтрТ.СоглашениеНаименование) + "|"
+ + Строка(СтрТ.ПартнерИдентификатор) + "|"
+ + СокрЛП(СтрТ.ПартнерНаименование) + "|"
+ + Строка(СтрТ.КонтрагентИдентификатор) + "|"
+ + СокрЛП(СтрТ.КонтрагентНаименование) + "|"
+ + Строка(СтрТ.ОрганизацияИдентификатор) + "|"
+ + Строка(СтрТ.СкладИдентификатор) + "|"
+ + Строка(СтрТ.ХарактеристикаИдентификатор) + "|"
+ + СокрЛП(СтрТ.ГТД) + "|"
+ + СокрЛП(СтрТ.Назначение) + "|"
+ + СокрЛП(СтрТ.ВидЗапасов);
+
+КонецФункции
+
+Функция СерияЗаполненаВСтрокеКомиссии(СтрТ)
+
+ Если ЗначениеЗаполнено(СтрТ.СерияИдентификатор) Тогда
+ Возврат Истина;
+ КонецЕсли;
+ Если Не ПустаяСтрока(СокрЛП(СтрТ.Серия)) Тогда
+ Возврат Истина;
+ КонецЕсли;
+ Возврат Не ПустаяСтрока(СокрЛП(СтрТ.СерияНомер));
+
+КонецФункции
+
Функция БулевоИзЗначения(Значение)
Если ТипЗнч(Значение) = Тип("Булево") Тогда
diff --git a/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form.xml b/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form.xml
index 6694e8e..208ba4b 100644
--- a/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form.xml
+++ b/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form.xml
@@ -21,6 +21,11 @@
Form.Command.СопоставитьДанные
+
+
@@ -1359,6 +1369,25 @@
СопоставитьДанные
+
+
+
+ ru
+ Свернуть комиссионные остатки
+
+
+
+
+ ru
+ Удалить взаимопогашающие строки комиссии (− без серии / + с серией) и группы с net qty ≈ 0
+
+
+
+ StdPicture.CollapseTree
+ true
+
+ СвернутьКомиссионныеОстатки
+
diff --git a/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form/Module.bsl b/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form/Module.bsl
index 47d0a94..de0e612 100644
--- a/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form/Module.bsl
+++ b/src/DataProcessors/РК_ЗагрузкаОстатков/Forms/Форма/Ext/Form/Module.bsl
@@ -179,6 +179,30 @@
КонецПроцедуры
+&НаКлиенте
+Процедура СвернутьКомиссионныеОстатки(Команда)
+
+ Если Объект.ВходящиеКомиссионныеТовары.Количество() = 0 Тогда
+ ПоказатьПредупреждение(, "Нет строк во входящих комиссионных товарах");
+ Возврат;
+ КонецЕсли;
+
+ Попытка
+ Рез = СвернутьКомиссионныеОстаткиНаСервере();
+ Исключение
+ ПоказатьПредупреждение(, КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
+ Возврат;
+ КонецПопытки;
+
+ ПоказатьОповещениеПользователя("Свернуть комиссионные остатки", ,
+ "Было: " + Формат(Рез.БылоСтрок, "ЧН=0; ЧГ=0")
+ + "; удалено: " + Формат(Рез.УдаленоСтрок, "ЧН=0; ЧГ=0")
+ + " (пар −/+: " + Формат(Рез.УдаленоПар, "ЧН=0; ЧГ=0")
+ + ", net≈0: " + Формат(Рез.УдаленоГруппNetZero, "ЧН=0; ЧГ=0") + ")"
+ + "; осталось: " + Формат(Рез.СталоСтрок, "ЧН=0; ЧГ=0"));
+
+КонецПроцедуры
+
&НаКлиенте
Процедура СоздатьДокументы(Команда)
@@ -475,6 +499,16 @@
КонецФункции
+&НаСервере
+Функция СвернутьКомиссионныеОстаткиНаСервере()
+
+ ОбъектОбработки = РеквизитФормыВЗначение("Объект");
+ Результат = ОбъектОбработки.СвернутьКомиссионныеОстатки();
+ ЗначениеВРеквизитФормы(ОбъектОбработки, "Объект");
+ Возврат Результат;
+
+КонецФункции
+
&НаСервере
Функция СоздатьДокументыНаСервере(РежимТест)