В практике разработки на платформе 1С:Предприятие часто возникает задача реализации партионного списания «вручную» (с использованием объектов ТаблицаЗначений). Одной из наиболее распространенных проблем является использование метода НайтиСтроки() для поиска данных в табличной части документа или таблице остатков. Основной риск заключается в том, что платформа 1С не гарантирует сохранение исходного порядка строк в возвращаемом массиве, что критично для алгоритмов FIFO или при специфических требованиях к последовательности списания (например, когда в документе важна группировка по цене).
В данной статье мы разберем, почему возникает эта проблема, проанализируем различные способы её решения и выберем наиболее оптимальные алгоритмы для сохранения порядка обхода данных.
Разберем ситуацию: метод НайтиСтроки() возвращает массив ссылок на строки исходной таблицы. Если для таблицы значений не заданы индексы, платформа выполняет линейный поиск. В этом случае порядок строк в массиве часто совпадает с физическим порядком в таблице, но это лишь побочный эффект реализации, который может измениться. Если же для таблицы построен индекс, метод использует его для ускорения поиска. Поскольку внутренние структуры индекса (хеш-таблицы или деревья) ориентированы на скорость доступа, а не на сохранение последовательности добавления, результат в массиве может оказаться «перемешанным».
В задачах партионного учета, где мы сопоставляем строки документа с остатками (связь «многие-ко-многим», если в документе одна номенклатура встречается по разным ценам), хаотичный порядок может привести к некорректному распределению сумм и остатков.
Рассмотрим подход, который часто встречается на технических форумах. Его суть заключается в том, чтобы после получения массива строк принудительно отсортировать его в соответствии с индексами строк в исходной таблице значений. Проанализируем пример функции, реализующей этот механизм:
Функция НайтиСтрокиССохранениемПорядка(ТЗ, СтруктураПоиска)
// Получаем стандартный массив найденных строк
НайденныеСтроки = ТЗ.НайтиСтроки(СтруктураПоиска);
Если НайденныеСтроки.Количество() = 0 Тогда
Возврат НайденныеСтроки;
КонецЕсли;
// Создаем временную таблицу для сортировки
ТЗ_Сортировка = Новый ТаблицаЗначений();
ТЗ_Сортировка.Колонки.Добавить("Индекс", Новый ОписаниеТипов("Число"));
ТЗ_Сортировка.Колонки.Добавить("СсылкаНаСтроку");
Для Каждого СтрокаМассива Из НайденныеСтроки Цикл
НоваяСтр = ТЗ_Сортировка.Добавить();
// Метод Индекс() возвращает порядковый номер строки в исходной ТЗ
НоваяСтр.Индекс = ТЗ.Индекс(СтрокаМассива);
НоваяСтр.СсылкаНаСтроку = СтрокаМассива;
КонецЦикла;
// Сортируем временную таблицу по возрастанию индекса
ТЗ_Сортировка.Сортировать("Индекс Возр");
// Формируем результирующий массив в правильном порядке
Результат = Новый Массив;
Для Каждого СтрСорт Из ТЗ_Сортировка Цикл
Результат.Добавить(СтрСорт.СсылкаНаСтроку);
КонецЦикла;
Возврат Результат;
КонецФункции
Важное замечание по производительности: Метод ТЗ.Индекс(Строка) внутри цикла работает за линейное время $O(n)$. Это означает, что для больших таблиц общая сложность алгоритма составит $O(n^2)$, что может существенно замедлить проведение документа. Если строк в таблице много, этот метод лучше оптимизировать.
Чтобы избежать медленного метода Индекс(), мы можем заранее подготовить исходную таблицу значений. Проанализируем этот алгоритм по шагам:
ТаблицаДокумента колонку СлужебныйПорядок с типом «Число».НайтиСтроки() выгрузим найденные строки в копию таблицы и отсортируем по колонке СлужебныйПорядок.Посмотрим на пример кода:
// 1. Подготовка таблицы
ТЗ.Колонки.Добавить("СлужебныйПорядок", Новый ОписаниеТипов("Число"));
Для Счетчик = 0 По ТЗ.Количество() - 1 Цикл
ТЗ[Счетчик].СлужебныйПорядок = Счетчик;
КонецЦикла;
// ... где-то в алгоритме списания ...
НайденныеСтроки = ТЗ.НайтиСтроки(НоваяСтруктура);
// Если порядок критичен, выгружаем в промежуточную ТЗ для сортировки
Если НайденныеСтроки.Количество() > 1 Тогда
ВремТЗ = ТЗ.Скопировать(НайденныеСтроки);
ВремТЗ.Сортировать("СлужебныйПорядок Возр");
// Работаем далее с ВремТЗ
КонецЕсли;
Если в алгоритме списания партий возникает связь «многие-ко-многим» (например, одна номенклатура в остатках по разным партиям и та же номенклатура в документе по разным ценам), лучшим решением будет использование запроса с временными таблицами. Это позволит платформе самой позаботиться об очередности и группировке данных.
Рассмотрим логику этого процесса:
НомерСтроки документа.Номенклатуре.УПОРЯДОЧИТЬ ПО: сначала по номеру строки документа, затем по дате партии (для FIFO).Этот способ исключает использование НайтиСтроки() в цикле и считается наиболее производительным и «правильным» с точки зрения архитектуры 1С.
Если вы хотите избежать запросов и работать только в коде, можно применить классический алгоритм Merge Join. Выясним причину его популярности: он позволяет обойти обе таблицы за один проход.
Алгоритм действий:
Номенклатура.Номенклатура (и по дате партии для FIFO).Такой подход гарантирует строгий порядок обработки и исключает неопределенность, которую вносит НайтиСтроки().
Подведем итоги. Метод НайтиСтроки() удобен, но опасен для партионного учета из-за непредсказуемости порядка. Для надежной работы рекомендуется:
Использование данных подходов позволит избежать трудноуловимых ошибок в расчетах себестоимости и обеспечит корректное списание партий в любой конфигурации.