Загрузка данных


////////////////////////////////////////////////////////////////////////////////
// Сопоставление номенклатуры чеков ЗПЧ -> БП, версия 4
// Задача Bitrix 68771 «Некорректная загрузка розничных чеков Авто-Точка, Авто-Мастер»
//
// Весь файл — валидный BSL: выводы вынесены в комментарии, его можно вставить
// в модуль формы целиком. Указания по встраиванию — в конце файла.
//
////////////////////////////////////////////////////////////////////////////////
//
//                        ЧАСТЬ I. ВЫВОДЫ ПО РАЗБОРУ
//
// Проверено по выгрузке конфигурации E:\1c\БП (типовой) (версия 3.0.198.15).
// Доступа к работающей информационной базе не было — где нужен запрос к данным,
// это отмечено явно.
//
//------------------------------------------------------------------------------
// 1. Расширения Автоточка и Автомастер разошлись
//------------------------------------------------------------------------------
//
// Документ «Передача_Claude_Code_загрузка_чеков.md» описывает старый приёмник.
// Это верно только для Автоточки. Автомастер уже переработан: в нём есть
// КонтекстПодбора (склад + дата остатков + накопитель Израсходовано +
// сбор ПроблемыПодбора), резервирование остатка и вывод проблемных строк.
//
// Главное отличие — время подбора. В Автомастере (Form/Module.bsl:1018):
//
//     // Здесь только раскладка складов ЗПЧ по складам БП. Номенклатура подбирается
//     // в СоздатьДокументыОРП: до подтверждения таблицы соответствия склад неизвестен,
//     // а остатки проверяются именно по складу документа.
//
// v3 это откатывал: он брал склад через СкладБППоСкладуЗПЧ в том же проходе,
// где СоответствиеСкладовЧекиОбработка только что заполнил строку автоподбором
// ПодобратьСклад. Если пользователь потом правит склад в форме — номенклатура
// уже подобрана по остаткам чужого склада. В v4 подбор выполняется одним
// пакетным проходом в начале СоздатьДокументыОРП, то есть после подтверждения
// таблицы соответствия. Это даёт и подтверждённый склад, и пакетную группировку.
//
//------------------------------------------------------------------------------
// 2. Субконто счёта 41.01 (пункт 7.1 исходного документа)
//------------------------------------------------------------------------------
//
// Из конфигурации состав определить нельзя, и это не оговорка:
// в ChartsOfAccounts\Хозрасчетный\Ext\Predefined.xml у 41.01 стоит
// <ExtDimensionTypes/> — пусто, и непустых вхождений в файле нет вообще.
// Состав субконто в БП 3.0 — данные базы («Настройка плана счетов»).
//
// Но канонический шаблон для группы счетов «Товары» задан в коде,
// CommonModules\ОбщегоНазначенияБП\Ext\Module.bsl:3893-3923:
//
//     1  Номенклатура   Параметр = Истина            — всегда
//     2  СтавкиНДС      Параметр = Ложь              — никогда
//     3  Склады         Параметр = "ВестиСкладскойУчет"
//     4  Партии         Параметр = "ВестиПартионныйУчет"
//
// Отсюда:
//   * Субконто1 = Номенклатура гарантированно. Допущение v3 верно.
//   * Склады — Субконто2 при включённом складском учёте.
//   * Партии могут быть Субконто2 (если складской выключен) или Субконто3.
//
// И вот здесь v3 ломался. Его запрос не передавал виды субконто (третий
// параметр виртуальной таблицы пустой) и не агрегировал. Виртуальная таблица
// Остатки возвращает строку на каждую комбинацию субконто, поэтому при
// включённом партионном учёте ЛЕВОЕ СОЕДИНЕНИЕ ... ПО К.Номенклатура = Ост.Субконто1
// давало по нескольку строк на одну карточку: в пул кандидатов попадали дубли
// с частичными остатками, себестоимость считалась по одной партии, Свободно
// занижалось. То же самое при выключенном отборе по складу и включённом
// складском учёте — дубли по складам.
//
// Типовой код так не делает: CommonModules\ИнформированиеОтрицательныеОстатки\
// Ext\Module.bsl:104,118 всегда передаёт массив видов субконто явно
// (&СубконтоНоменклатураСклад) и разыменовывает через ВЫРАЗИТЬ(... КАК Справочник.Склады).
//
// В v4 виды субконто передаются массивом. Побочный эффект: индекс Субконто2
// становится детерминированным, и самописный ИндексСубконтоСклады() не нужен.
//
//------------------------------------------------------------------------------
// 3. v3 был регрессом относительно того, что уже написано в Автомастере
//------------------------------------------------------------------------------
//
// Автомастер, Form/Module.bsl:1144-1203, ПолучитьОстатокНоменклатуры:
//
//   признак               Автомастер (как есть)              v3
//   --------------------  ---------------------------------  ------------------
//   виды субконто         &ВидыСубконто массивом             не передаются
//   организация           Организация = &Организация         отбора нет
//   учёт по складам       БухгалтерскийУчет.                 самописный
//                         ВедетсяУчетПоСкладам(Счет)         ИндексСубконтоСклады()
//   счета                 СчетаУчетаТоваров(): 41.01,        только 41.01
//                         41.02, 41.11
//
// Отбор по организации — не теория: реквизит Организация на форме есть
// (Form/Module.bsl:5) и используется во всех остальных запросах формы.
// ВедетсяУчетПоСкладам — штатная функция поверх кэша
// БухгалтерскийУчетВызовСервераПовтИсп.ПолучитьСвойстваСчета()
// (CommonModules\БухгалтерскийУчет\Ext\Module.bsl:478). v3 вместо неё на каждый
// запрос кандидатов делал НайтиПоКоду("41.01") и читал табличную часть
// Счет.ВидыСубконто из базы.
//
// В легаси Автоточки, к слову, Счет4111 вычисляется и не используется
// (Form/Module.bsl:1233).
//
// Все четыре пункта в v4 исправлены.
//
//------------------------------------------------------------------------------
// 4. Дефект логики: ранговое сопоставление вырождалось при остатке > 1
//------------------------------------------------------------------------------
//
// В v3 строка брала ПЕРВОГО кандидата, у которого хватает свободного остатка.
// Это не «ранг к рангу».
//
// Эталонный пример из задачи проходил только потому, что у обеих карточек
// остаток ровно 1. Если бы у карточки с себестоимостью 1060,36 было 2 шт:
//
//     строка 1730 -> карточка 1060,36   (Свободно 2 -> 1)
//     строка 1610 -> первый кандидат снова подходит -> ТА ЖЕ карточка 1060,36
//
// Обе строки на одной карточке, остатка хватает, документ проводится, ошибки
// нет — себестоимость просто списалась не с той карточки. Молчаливая версия
// исходного бага. Открытый вопрос №2 в документе описывает другой случай
// (близкая себестоимость), этот не был покрыт.
//
// В v4 раздача в два прохода:
//   проход 1 — каждой строке своя, ещё не занятая в этой группе карточка
//              (строгий ранг к рангу);
//   проход 2 — добор: строки, которым отдельной карточки не хватило, садятся
//              на самую дорогую карточку со свободным остатком, повтор разрешён.
//
//------------------------------------------------------------------------------
// 5. Артикулы с обрезкой (пункт 7.3) — подтверждается, и сильнее чем ожидалось
//------------------------------------------------------------------------------
//
// Артикул в Catalogs\Номенклатура.xml:401-415 — строка(50), переменная.
// То есть обрезка до 25 не схемная, а импортная.
//
// Но Лев(КатНомер, 25) — НЕ мёртвый DBF-код. Он в живом канале реализаций,
// в обоих расширениях:
//   Раширения\{Автоточка,Автомастер}\Documents\РеализацияТоваровУслуг\
//   Forms\ФормаДокументаОбщая\Ext\Form\Module.bsl:443,459,470
// везде «Номенклатура.Артикул = &Артикул» с Лев(Артикул,25) в параметре.
//
// Прямое следствие для открытого вопроса №3: v4, как и v3, сравнивает артикул
// полностью, значит карточки с исторически обрезанными артикулами фаза 1
// не найдёт и строки посыплются в фазу 2. Проверять в базе:
//
//     ВЫБРАТЬ Ссылка, Артикул ИЗ Справочник.Номенклатура
//     ГДЕ ДлинаСтроки(Артикул) = 25 И НЕ ПометкаУдаления И НЕ ЭтоГруппа
//
// Сознательно НЕ добавляю обрезку в подбор: она превращает точное сравнение
// в префиксное и порождает ложные срабатывания. Если обрезанные карточки
// действительно есть — это задача на чистку справочника.
//
//------------------------------------------------------------------------------
// 6. Мелкие дефекты v3, исправленные в v4
//------------------------------------------------------------------------------
//
//   * ФолбэкБезОстатка не пополнял накопитель Расход — выданная фолбэком
//     карточка не учитывалась дальше по пакету.
//   * Приоритет сортировался по-разному в основном подборе
//     ("Себестоимость Убыв, Приоритет Возр") и в фолбэке
//     ("Приоритет Возр, Себестоимость Убыв"). В основном подборе приоритет
//     по наименованию фактически не работал — только тайбрейкер при равной
//     себестоимости.
//   * В пул кандидатов не отсекались группы справочника (НЕ ЭтоГруппа).
//   * Трюк с латинской «o» в КоличествoОстатокДт и СтрЗаменить убран.
//   * КандидатыПоКлючу делал один запрос на группу. На 88 строках это десятки
//     запросов плюс столько же чтений Счет.ВидыСубконто. В v4 всё собрано
//     в пакет: 1 запрос к справочнику + (число дат x число счетов) запросов
//     остатков. Для типовой загрузки за один день — 4 запроса.
//   * Документы, склад которых не сопоставлен, в подборе не участвуют
//     (в СоздатьДокументыОРП они всё равно пропускаются) — иначе они
//     съедали бы остаток у реальных документов.
//
//------------------------------------------------------------------------------
// 7. Что осталось непроверенным — нужны данные базы
//------------------------------------------------------------------------------
//
//   * фактический состав субконто 41.01 (пункт 7.1);
//   * дубли карточек по артикулу 252909 (пункт 7.2);
//   * карточки под одним наименованием с разными артикулами (пункт 7.4);
//   * 25 строк, легших на карточку с другим наименованием (пункт 7.5);
//   * три карточки, использованные дважды (пункт 7.6).
//
// Всё это проверяется прогоном с ВыводитьДиагностику = Истина на эталонном
// документе ООБП-000452 от 05.06.2026.
//
//------------------------------------------------------------------------------
// 8. Что осталось нерешённым по существу
//------------------------------------------------------------------------------
//
//   * Счета 41.02 и 41.11. v4 берёт их из СчетаУчетаТоваров() вслед за
//     Автомастером. Для розницы это может быть существенно — подтвердить
//     у владельца задачи, какие счета учёта реально используются.
//   * Смешанная конфигурация счетов (на одном ведётся учёт по складам,
//     на другом нет) обрабатывается так: остатки счёта без складской
//     аналитики кладутся в «пустой склад» и добавляются к остатку любого
//     склада. Накопитель Расход при этом ключуется по складу, поэтому
//     теоретически возможна переотдача такого остатка между складами.
//     Это ровно та же семантика, что уже реализована в Автомастере;
//     менять её без подтверждения не стал.
//   * Если у двух карточек одного артикула близкая себестоимость,
//     ранжирование по цене вырождается в произвольный выбор. Расхождение
//     самоустраняется при распродаже обеих позиций (открытый вопрос №2
//     исходного документа остаётся открытым).
//
////////////////////////////////////////////////////////////////////////////////
//
//                        ЧАСТЬ II. КОД
//
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Точка входа. Вызывается один раз из СоздатьДокументыОРП, после того как
// таблица СоответствиеСкладовЧеки подтверждена пользователем.
//
// Заполняет в каждой структуре строки товара реквизит НоменклатураБП.
// Возвращает массив структур с проблемными строками — формат совпадает
// с тем, что печатает ВывестиПроблемыПодбора.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция ПодобратьНоменклатуруПакета(МассивДокументов, ВыводитьДиагностику = Ложь)

	Проблемы = Новый Массив;

	Строки = СобратьСтрокиПакета(МассивДокументов);
	Если Строки.Количество() = 0 Тогда
		Возврат Проблемы;
	КонецЕсли;

	Индексы = ЗагрузитьКарточкиКандидатов(Строки);
	Остатки = ЗагрузитьОстаткиКандидатов(Строки, Индексы.Карточки);

	// "Номенклатура|Склад" -> уже разобранное количество в этом пакете
	Расход = Новый Соответствие;

	Если ВыводитьДиагностику Тогда
		ОбщегоНазначения.СообщитьПользователю(
			СтрШаблон("ДИАГНОСТИКА: строк в пакете %1, карточек-кандидатов %2",
				Формат(Строки.Количество(), "ЧГ=0"),
				Формат(Индексы.Карточки.Количество(), "ЧГ=0")));
	КонецЕсли;

	// Фаза 1 — пул строго по артикулу, только карточки со свободным остатком.
	ОбработатьФазу(Строки, Индексы, Остатки, Расход, "Артикул", ВыводитьДиагностику);

	// Фаза 2 — пул по наименованию и наименованию поставщика (точное равенство).
	// Здесь разбирается случай «одно наименование, разные артикулы».
	ОбработатьФазу(Строки, Индексы, Остатки, Расход, "Наименование", ВыводитьДиагностику);

	// Фаза 3 — фолбэк без остатка, с регистрацией проблемы.
	ФолбэкБезОстатка(Строки, Индексы, Остатки, Расход, Проблемы, ВыводитьДиагностику);

	Возврат Проблемы;

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Проход сбора: все строки всех документов пакета в одну плоскую таблицу.
// В ДанныеДокументов нет ссылки на документ — только ДатаДокумента, Склад
// и Товары (массив структур), поэтому документ идентифицируется порядковым
// номером в пакете.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция СобратьСтрокиПакета(МассивДокументов)

	Строки = Новый ТаблицаЗначений;
	Строки.Колонки.Добавить("Данные");                                          // ссылка на структуру строки товара
	Строки.Колонки.Добавить("СкладБП");
	Строки.Колонки.Добавить("ДатаОстатков",    Новый ОписаниеТипов("Дата"));
	Строки.Колонки.Добавить("Артикул",         Новый ОписаниеТипов("Строка"));
	Строки.Колонки.Добавить("Наименование",    Новый ОписаниеТипов("Строка"));
	Строки.Колонки.Добавить("НаимПоставщика",  Новый ОписаниеТипов("Строка"));
	Строки.Колонки.Добавить("Количество",      Новый ОписаниеТипов("Число"));
	Строки.Колонки.Добавить("Цена",            Новый ОписаниеТипов("Число"));
	Строки.Колонки.Добавить("КлючАртикул",     Новый ОписаниеТипов("Строка"));
	Строки.Колонки.Добавить("КлючНаименование", Новый ОписаниеТипов("Строка"));
	Строки.Колонки.Добавить("Разобрана",       Новый ОписаниеТипов("Булево"));

	ИндексДокумента = 0;

	Для Каждого СтрокаДокумента Из МассивДокументов Цикл

		ИндексДокумента = ИндексДокумента + 1;

		Если НЕ СтрокаДокумента.Свойство("Товары") Тогда
			Продолжить;
		КонецЕсли;

		СкладБП = СкладБППоСкладуЗПЧ(СтрокаДокумента.Склад);

		// Документы без сопоставленного склада в СоздатьДокументыОРП пропускаются.
		// Если пустить их в подбор, они съедят остаток у реальных документов.
		Если НЕ ЗначениеЗаполнено(СкладБП) Тогда
			Продолжить;
		КонецЕсли;

		ДатаДокумента = ДатаДокументаСтроки(СтрокаДокумента);

		Для Каждого СтрокаТовара Из СтрокаДокумента.Товары Цикл

			// сбрасываем результат прошлой загрузки, если структура переиспользуется
			СтрокаТовара.Вставить("НоменклатураБП",   Неопределено);
			СтрокаТовара.Вставить("ХарактеристикаБП", Неопределено);

			Новая = Строки.Добавить();
			Новая.Данные         = СтрокаТовара;
			Новая.СкладБП        = СкладБП;
			Новая.ДатаОстатков   = КонецДня(ДатаДокумента) + 1;
			Новая.Артикул        = ЗначениеСтроки(СтрокаТовара, "КаталожныйНомерНоменклатуры", "Артикул");
			Новая.Наименование   = ЗначениеСтроки(СтрокаТовара, "Номенклатура");
			Новая.НаимПоставщика = ЗначениеСтроки(СтрокаТовара, "НоменклатураПоставщика");

			Кол = ЧисловоеЗначение(СтрокаТовара, "Количество");
			Новая.Количество = ?(Кол > 0, Кол, 1);
			Новая.Цена       = ЧисловоеЗначение(СтрокаТовара, "Цена");

			ОсноваИмени = ?(ЗначениеЗаполнено(Новая.Наименование),
				Новая.Наименование, Новая.НаимПоставщика);

			Префикс = Формат(ИндексДокумента, "ЧГ=0") + "|" + КлючСклада(СкладБП) + "|";
			Новая.КлючАртикул      = Префикс + "А:" + ВРег(Новая.Артикул);
			Новая.КлючНаименование = Префикс + "Н:" + ВРег(ОсноваИмени);

		КонецЦикла;
	КонецЦикла;

	Возврат Строки;

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Один запрос к справочнику на весь пакет. Возвращает структуру с индексами:
//   ПоАртикулу      ВРег(артикул)      -> массив ссылок
//   ПоНаименованию  ВРег(наименование) -> массив ссылок
//   Карточки        массив всех найденных ссылок (для запроса остатков)
//   Свойства        ссылка -> Структура(Наименование, Артикул)
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция ЗагрузитьКарточкиКандидатов(Строки)

	ПоАртикулу     = Новый Соответствие;
	ПоНаименованию = Новый Соответствие;
	Карточки       = Новый Массив;
	Свойства       = Новый Соответствие;

	Индексы = Новый Структура("ПоАртикулу, ПоНаименованию, Карточки, Свойства",
		ПоАртикулу, ПоНаименованию, Карточки, Свойства);

	Артикулы     = Новый Массив;
	Наименования = Новый Массив;
	УникА        = Новый Соответствие;
	УникН        = Новый Соответствие;

	Для Каждого С Из Строки Цикл

		Если ЗначениеЗаполнено(С.Артикул) И УникА[ВРег(С.Артикул)] = Неопределено Тогда
			УникА.Вставить(ВРег(С.Артикул), Истина);
			Артикулы.Добавить(С.Артикул);
		КонецЕсли;

		Если ЗначениеЗаполнено(С.Наименование) И УникН[ВРег(С.Наименование)] = Неопределено Тогда
			УникН.Вставить(ВРег(С.Наименование), Истина);
			Наименования.Добавить(С.Наименование);
		КонецЕсли;

		Если ЗначениеЗаполнено(С.НаимПоставщика) И УникН[ВРег(С.НаимПоставщика)] = Неопределено Тогда
			УникН.Вставить(ВРег(С.НаимПоставщика), Истина);
			Наименования.Добавить(С.НаимПоставщика);
		КонецЕсли;

	КонецЦикла;

	Если Артикулы.Количество() = 0 И Наименования.Количество() = 0 Тогда
		Возврат Индексы;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Ном.Ссылка       КАК Номенклатура,
	|	Ном.Наименование КАК Наименование,
	|	Ном.Артикул      КАК Артикул
	|ИЗ
	|	Справочник.Номенклатура КАК Ном
	|ГДЕ
	|	НЕ Ном.ПометкаУдаления
	|	И НЕ Ном.ЭтоГруппа
	|	И (Ном.Артикул В (&Артикулы)
	|			ИЛИ Ном.Наименование В (&Наименования))";

	Запрос.УстановитьПараметр("Артикулы",     Артикулы);
	Запрос.УстановитьПараметр("Наименования", Наименования);

	Попытка
		Выборка = Запрос.Выполнить().Выбрать();
	Исключение
		ОбщегоНазначения.СообщитьПользователю(
			"Ошибка запроса кандидатов по справочнику номенклатуры: " + ОписаниеОшибки());
		Возврат Индексы;
	КонецПопытки;

	Пока Выборка.Следующий() Цикл

		Свойства.Вставить(Выборка.Номенклатура,
			Новый Структура("Наименование, Артикул",
				СокрЛП(Выборка.Наименование), СокрЛП(Выборка.Артикул)));
		Карточки.Добавить(Выборка.Номенклатура);

		ДобавитьВИндекс(ПоАртикулу,     ВРег(СокрЛП(Выборка.Артикул)),      Выборка.Номенклатура);
		ДобавитьВИндекс(ПоНаименованию, ВРег(СокрЛП(Выборка.Наименование)), Выборка.Номенклатура);

	КонецЦикла;

	Возврат Индексы;

КонецФункции

&НаСервере
Процедура ДобавитьВИндекс(Индекс, Ключ, Ссылка)

	Если ПустаяСтрока(Ключ) Тогда
		Возврат;
	КонецЕсли;

	Список = Индекс[Ключ];
	Если Список = Неопределено Тогда
		Список = Новый Массив;
		Индекс.Вставить(Ключ, Список);
	КонецЕсли;
	Список.Добавить(Ссылка);

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Остатки всех кандидатов сразу: по одному запросу на (дата остатков, счёт).
//
// Виды субконто передаются массивом явно — так делает типовой код
// (ИнформированиеОтрицательныеОстатки). Это даёт две вещи:
//   * агрегацию по партиям, если партионный учёт включён (иначе виртуальная
//     таблица вернула бы строку на каждую партию);
//   * детерминированный индекс Субконто2 = Склады, без поиска индекса вручную.
//
// Отбор по организации обязателен: Хозрасчетный — регистр с измерением
// Организация, база может быть многофирменной.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция ЗагрузитьОстаткиКандидатов(Строки, Карточки)

	// "Номенклатура|Склад|Дата" -> Структура(Количество, Сумма)
	Остатки = Новый Соответствие;

	Если Карточки.Количество() = 0 Тогда
		Возврат Остатки;
	КонецЕсли;

	Даты     = Новый Массив;
	УникДаты = Новый Соответствие;
	Для Каждого С Из Строки Цикл
		Если УникДаты[С.ДатаОстатков] = Неопределено Тогда
			УникДаты.Вставить(С.ДатаОстатков, Истина);
			Даты.Добавить(С.ДатаОстатков);
		КонецЕсли;
	КонецЦикла;

	ВидНоменклатура = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура;
	ВидСклады       = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Склады;
	ПустойСклад     = Справочники.Склады.ПустаяСсылка();

	Для Каждого Счет Из СчетаУчетаТоваров() Цикл

		ПоСкладам = БухгалтерскийУчет.ВедетсяУчетПоСкладам(Счет);

		ВидыСубконто = Новый Массив;
		ВидыСубконто.Добавить(ВидНоменклатура);
		Если ПоСкладам Тогда
			ВидыСубконто.Добавить(ВидСклады);
		КонецЕсли;

		ТекстЗапроса =
		"ВЫБРАТЬ
		|	ВЫРАЗИТЬ(Ост.Субконто1 КАК Справочник.Номенклатура) КАК Номенклатура,
		|	[ПОЛЕ_СКЛАДА] КАК Склад,
		|	СУММА(Ост.КоличествоОстатокДт) КАК Количество,
		|	СУММА(Ост.СуммаОстатокДт)      КАК Сумма
		|ИЗ
		|	РегистрБухгалтерии.Хозрасчетный.Остатки(
		|			&ДатаОстатков,
		|			Счет = &Счет,
		|			&ВидыСубконто,
		|			Организация = &Организация
		|				И Субконто1 В (&Карточки)) КАК Ост
		|СГРУППИРОВАТЬ ПО
		|	ВЫРАЗИТЬ(Ост.Субконто1 КАК Справочник.Номенклатура)[ГРУППИРОВКА_СКЛАДА]";

		Если ПоСкладам Тогда
			ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ПОЛЕ_СКЛАДА]",
				"ВЫРАЗИТЬ(Ост.Субконто2 КАК Справочник.Склады)");
			ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ГРУППИРОВКА_СКЛАДА]",
				"," + Символы.ПС + "	ВЫРАЗИТЬ(Ост.Субконто2 КАК Справочник.Склады)");
		Иначе
			ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ПОЛЕ_СКЛАДА]",
				"ЗНАЧЕНИЕ(Справочник.Склады.ПустаяСсылка)");
			ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "[ГРУППИРОВКА_СКЛАДА]", "");
		КонецЕсли;

		Для Каждого ДатаОст Из Даты Цикл

			Запрос = Новый Запрос(ТекстЗапроса);
			Запрос.УстановитьПараметр("ДатаОстатков", ДатаОст);
			Запрос.УстановитьПараметр("Счет",         Счет);
			Запрос.УстановитьПараметр("ВидыСубконто", ВидыСубконто);
			Запрос.УстановитьПараметр("Организация",  Организация);
			Запрос.УстановитьПараметр("Карточки",     Карточки);

			Попытка
				Выборка = Запрос.Выполнить().Выбрать();
			Исключение
				ОбщегоНазначения.СообщитьПользователю(
					СтрШаблон("Ошибка запроса остатков по счету %1 на %2: %3",
						Строка(Счет), Формат(ДатаОст, "ДЛФ=D"), ОписаниеОшибки()));
				Продолжить;
			КонецПопытки;

			Пока Выборка.Следующий() Цикл

				СкладОстатка = ?(ПоСкладам, Выборка.Склад, ПустойСклад);
				Ключ = КлючОстатка(Выборка.Номенклатура, СкладОстатка, ДатаОст);

				Текущий = Остатки[Ключ];
				Если Текущий = Неопределено Тогда
					Текущий = Новый Структура("Количество, Сумма", 0, 0);
					Остатки.Вставить(Ключ, Текущий);
				КонецЕсли;

				Текущий.Количество = Текущий.Количество + Выборка.Количество;
				Текущий.Сумма      = Текущий.Сумма      + Выборка.Сумма;

			КонецЦикла;

		КонецЦикла;
	КонецЦикла;

	Возврат Остатки;

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Остаток и себестоимость карточки на складе. Остатки счетов без складской
// аналитики лежат под «пустым складом» и добавляются к остатку любого склада.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция ОстатокКарточки(Остатки, Номенклатура, Склад, ДатаОстатков)

	Количество = 0;
	Сумма      = 0;

	Данные = Остатки[КлючОстатка(Номенклатура, Склад, ДатаОстатков)];
	Если Данные <> Неопределено Тогда
		Количество = Количество + Данные.Количество;
		Сумма      = Сумма      + Данные.Сумма;
	КонецЕсли;

	Если ЗначениеЗаполнено(Склад) Тогда
		Данные = Остатки[КлючОстатка(Номенклатура, Справочники.Склады.ПустаяСсылка(), ДатаОстатков)];
		Если Данные <> Неопределено Тогда
			Количество = Количество + Данные.Количество;
			Сумма      = Сумма      + Данные.Сумма;
		КонецЕсли;
	КонецЕсли;

	Возврат Новый Структура("Остаток, Себестоимость",
		Количество, ?(Количество > 0, Сумма / Количество, 0));

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Фаза подбора. Группы: (документ, склад, артикул) или (документ, склад, наименование).
// У карточки один артикул и одно наименование, поэтому пулы внутри фазы
// не пересекаются — гонки между группами за одну карточку нет, а точное
// совпадение артикула выигрывает глобально.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Процедура ОбработатьФазу(Строки, Индексы, Остатки, Расход, Режим, ВыводитьДиагностику)

	ИмяКлюча = ?(Режим = "Артикул", "КлючАртикул", "КлючНаименование");

	Ключи = Новый Массив;
	Уник  = Новый Соответствие;

	Для Каждого С Из Строки Цикл

		Если С.Разобрана Тогда
			Продолжить;
		КонецЕсли;

		Если Режим = "Артикул" И НЕ ЗначениеЗаполнено(С.Артикул) Тогда
			Продолжить;
		КонецЕсли;

		Если Режим = "Наименование"
			И НЕ ЗначениеЗаполнено(С.Наименование)
			И НЕ ЗначениеЗаполнено(С.НаимПоставщика) Тогда
			Продолжить;
		КонецЕсли;

		Если Уник[С[ИмяКлюча]] = Неопределено Тогда
			Уник.Вставить(С[ИмяКлюча], Истина);
			Ключи.Добавить(С[ИмяКлюча]);
		КонецЕсли;

	КонецЦикла;

	Для Каждого Ключ Из Ключи Цикл

		Отбор  = Новый Структура(ИмяКлюча + ", Разобрана", Ключ, Ложь);
		Группа = Строки.НайтиСтроки(Отбор);
		Если Группа.Количество() = 0 Тогда
			Продолжить;
		КонецЕсли;

		РаспределитьГруппу(Группа, Индексы, Остатки, Расход, Режим, ВыводитьДиагностику);

	КонецЦикла;

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Раздача группы строк по кандидатам: дороже продано <-> дороже куплено.
//
// Два прохода. Первый — строгий ранг к рангу: каждой строке своя, ещё не занятая
// карточка. Именно этого не хватало в v3: там строка брала первого подходящего
// кандидата, и при остатке > 1 обе строки садились на одну карточку, себестоимость
// списывалась не с той. Второй проход — добор для строк, которым отдельной
// карточки не хватило.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Процедура РаспределитьГруппу(Группа, Индексы, Остатки, Расход, Режим, ВыводитьДиагностику)

	Образец   = Группа[0];
	Кандидаты = ПулКандидатов(Индексы, Остатки, Расход, Режим, Образец, Истина);

	Если Кандидаты.Количество() = 0 Тогда
		Возврат; // строки останутся неразобранными, попадут в следующую фазу
	КонецЕсли;

	Кандидаты.Сортировать("Себестоимость Убыв, Приоритет Возр, Наименование Возр");
	Порядок = СтрокиПоУбываниюЦены(Группа);

	// Проход 1 — ранговый.
	Для Каждого С Из Порядок Цикл

		Выбранный = Неопределено;
		Для Каждого К Из Кандидаты Цикл
			Если НЕ К.Занят И К.Свободно >= С.Количество Тогда
				Выбранный = К;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Выбранный <> Неопределено Тогда
			Выбранный.Занят = Истина;
			ЗакрепитьСтроку(С, Выбранный, Расход, Режим, "ранг", ВыводитьДиагностику);
		КонецЕсли;

	КонецЦикла;

	// Проход 2 — добор, повторное использование карточки разрешено.
	Для Каждого С Из Порядок Цикл

		Если С.Разобрана Тогда
			Продолжить;
		КонецЕсли;

		Выбранный = Неопределено;
		Для Каждого К Из Кандидаты Цикл
			Если К.Свободно >= С.Количество Тогда
				Выбранный = К;
				Прервать;
			КонецЕсли;
		КонецЦикла;

		Если Выбранный = Неопределено Тогда
			Продолжить; // уйдёт в следующую фазу
		КонецЕсли;

		ЗакрепитьСтроку(С, Выбранный, Расход, Режим, "добор", ВыводитьДиагностику);

	КонецЦикла;

КонецПроцедуры

&НаСервере
Процедура ЗакрепитьСтроку(С, Кандидат, Расход, Режим, Проход, ВыводитьДиагностику)

	Кандидат.Свободно = Кандидат.Свободно - С.Количество;

	Ключ     = КлючРасхода(Кандидат.Номенклатура, С.СкладБП);
	УжеВзято = Расход[Ключ];
	Расход.Вставить(Ключ, ?(УжеВзято = Неопределено, 0, УжеВзято) + С.Количество);

	С.Данные.Вставить("НоменклатураБП", Кандидат.Номенклатура);
	С.Разобрана = Истина;

	Если ВыводитьДиагностику Тогда
		ОбщегоНазначения.СообщитьПользователю(
			СтрШаблон("[%1/%2] ""%3"" (цена %4, кол-во %5) -> ""%6"" (себест. %7, остаток %8)",
				Режим, Проход, С.Наименование,
				Формат(С.Цена, "ЧДЦ=2"), Формат(С.Количество, "ЧДЦ=3"),
				Строка(Кандидат.Номенклатура),
				Формат(Кандидат.Себестоимость, "ЧДЦ=2"),
				Формат(Кандидат.Остаток, "ЧДЦ=3")));
	КонецЕсли;

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Пул кандидатов из заранее загруженных индексов. Запросов не делает.
// ТолькоСоСвободным = Ложь используется фолбэком.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция ПулКандидатов(Индексы, Остатки, Расход, Режим, Образец, ТолькоСоСвободным)

	Результат = Новый ТаблицаЗначений;
	Результат.Колонки.Добавить("Номенклатура");
	Результат.Колонки.Добавить("Наименование",  Новый ОписаниеТипов("Строка"));
	Результат.Колонки.Добавить("Остаток",       Новый ОписаниеТипов("Число"));
	Результат.Колонки.Добавить("Себестоимость", Новый ОписаниеТипов("Число"));
	Результат.Колонки.Добавить("Свободно",      Новый ОписаниеТипов("Число"));
	Результат.Колонки.Добавить("Приоритет",     Новый ОписаниеТипов("Число"));
	Результат.Колонки.Добавить("Занят",         Новый ОписаниеТипов("Булево"));

	Ссылки      = Новый Массив;
	Добавленные = Новый Соответствие;

	Если Режим = "Артикул" Тогда
		СобратьСсылки(Ссылки, Добавленные, Индексы.ПоАртикулу[ВРег(Образец.Артикул)]);
	Иначе
		СобратьСсылки(Ссылки, Добавленные, Индексы.ПоНаименованию[ВРег(Образец.Наименование)]);
		СобратьСсылки(Ссылки, Добавленные, Индексы.ПоНаименованию[ВРег(Образец.НаимПоставщика)]);
	КонецЕсли;

	Для Каждого Ссылка Из Ссылки Цикл

		ДанныеОстатка = ОстатокКарточки(Остатки, Ссылка, Образец.СкладБП, Образец.ДатаОстатков);

		УжеВзято = Расход[КлючРасхода(Ссылка, Образец.СкладБП)];
		Свободно = ДанныеОстатка.Остаток - ?(УжеВзято = Неопределено, 0, УжеВзято);

		Если ТолькоСоСвободным И Свободно <= 0 Тогда
			Продолжить;
		КонецЕсли;

		Свойства     = Индексы.Свойства[Ссылка];
		НаимКарточки = ?(Свойства = Неопределено, Строка(Ссылка), Свойства.Наименование);

		Стр = Результат.Добавить();
		Стр.Номенклатура  = Ссылка;
		Стр.Наименование  = НаимКарточки;
		Стр.Остаток       = ДанныеОстатка.Остаток;
		Стр.Себестоимость = ДанныеОстатка.Себестоимость;
		Стр.Свободно      = Свободно;
		Стр.Приоритет     = ПриоритетКарточки(НаимКарточки, Образец);

	КонецЦикла;

	Возврат Результат;

КонецФункции

&НаСервере
Процедура СобратьСсылки(Ссылки, Добавленные, Список)

	Если Список = Неопределено Тогда
		Возврат;
	КонецЕсли;

	Для Каждого Ссылка Из Список Цикл
		Если Добавленные[Ссылка] = Неопределено Тогда
			Добавленные.Вставить(Ссылка, Истина);
			Ссылки.Добавить(Ссылка);
		КонецЕсли;
	КонецЦикла;

КонецПроцедуры

&НаСервере
Функция ПриоритетКарточки(НаименованиеКарточки, Образец)

	Если ВРег(НаименованиеКарточки) = ВРег(Образец.Наименование) Тогда
		Возврат 0;
	ИначеЕсли ЗначениеЗаполнено(Образец.НаимПоставщика)
		И ВРег(НаименованиеКарточки) = ВРег(Образец.НаимПоставщика) Тогда
		Возврат 1;
	КонецЕсли;

	Возврат 2;

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Фаза 3. Свободных остатков нет ни у одного кандидата. Подставляем карточку,
// чтобы строка не осталась пустой, и регистрируем проблему.
// Накопитель Расход пополняем и здесь — в v3 этого не было, и следующая строка
// пакета считала этот остаток свободным.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Процедура ФолбэкБезОстатка(Строки, Индексы, Остатки, Расход, Проблемы, ВыводитьДиагностику)

	ВыданныеФолбэком = Новый Соответствие;

	Для Каждого С Из Строки Цикл

		Если С.Разобрана Тогда
			Продолжить;
		КонецЕсли;

		Кандидаты = ПулКандидатов(Индексы, Остатки, Расход, "Артикул", С, Ложь);
		Если Кандидаты.Количество() = 0 Тогда
			Кандидаты = ПулКандидатов(Индексы, Остатки, Расход, "Наименование", С, Ложь);
		КонецЕсли;

		Если Кандидаты.Количество() = 0 Тогда
			Проблемы.Добавить(НоваяПроблема(С, 0, "номенклатура в базе не найдена"));
			ОбщегоНазначения.СообщитьПользователю(
				СтрШаблон("Артикул ""%1"" (%2): номенклатура в базе не найдена",
					?(ЗначениеЗаполнено(С.Артикул), С.Артикул, "—"), С.Наименование));
			Продолжить;
		КонецЕсли;

		Кандидаты.Сортировать("Приоритет Возр, Свободно Убыв, Себестоимость Убыв");

		Выбранный = Неопределено;
		Для Каждого К Из Кандидаты Цикл
			Если ВыданныеФолбэком[К.Номенклатура] = Неопределено Тогда
				Выбранный = К;
				Прервать;
			КонецЕсли;
		КонецЦикла;
		Если Выбранный = Неопределено Тогда
			Выбранный = Кандидаты[0];
		КонецЕсли;

		ВыданныеФолбэком.Вставить(Выбранный.Номенклатура, Истина);
		ДоступноБыло = Выбранный.Свободно;

		ЗакрепитьСтроку(С, Выбранный, Расход, "Фолбэк", "без остатка", ВыводитьДиагностику);

		Проблемы.Добавить(НоваяПроблема(С, ДоступноБыло,
			СтрШаблон("свободных остатков нет ни у одного из %1 кандидатов, подставлена ""%2""",
				Формат(Кандидаты.Количество(), "ЧГ=0"), Строка(Выбранный.Номенклатура))));

		ОбщегоНазначения.СообщитьПользователю(
			СтрШаблон("ВНИМАНИЕ: ""%1"" (арт. %2, кол-во %3) — свободных остатков нет ни у одного из %4 кандидатов. Подставлена ""%5"", требуется ручная проверка",
				С.Наименование,
				?(ЗначениеЗаполнено(С.Артикул), С.Артикул, "—"),
				Формат(С.Количество, "ЧДЦ=3"),
				Формат(Кандидаты.Количество(), "ЧГ=0"),
				Строка(Выбранный.Номенклатура)));

		Если ВыводитьДиагностику Тогда
			Для Каждого К Из Кандидаты Цикл
				ОбщегоНазначения.СообщитьПользователю(
					СтрШаблон("    кандидат ""%1"": остаток %2, свободно %3, себест. %4, приоритет %5",
						Строка(К.Номенклатура),
						Формат(К.Остаток, "ЧДЦ=3"),
						Формат(К.Свободно, "ЧДЦ=3"),
						Формат(К.Себестоимость, "ЧДЦ=2"),
						Формат(К.Приоритет, "ЧГ=0")));
			КонецЦикла;
		КонецЕсли;

	КонецЦикла;

КонецПроцедуры

&НаСервере
Функция НоваяПроблема(С, Доступно, Причина)

	Проблема = Новый Структура;
	Проблема.Вставить("Склад",        С.СкладБП);
	Проблема.Вставить("Наименование", С.Наименование);
	Проблема.Вставить("Артикул",      С.Артикул);
	Проблема.Вставить("Требуется",    С.Количество);
	Проблема.Вставить("Доступно",     Доступно);
	Проблема.Вставить("Причина",      Причина);

	Возврат Проблема;

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// Вспомогательные
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция СтрокиПоУбываниюЦены(Группа)

	Сорт = Новый ТаблицаЗначений;
	Сорт.Колонки.Добавить("Строка");
	Сорт.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число"));

	Для Каждого С Из Группа Цикл
		Н = Сорт.Добавить();
		Н.Строка = С;
		Н.Цена   = С.Цена;
	КонецЦикла;
	Сорт.Сортировать("Цена Убыв");

	Результат = Новый Массив;
	Для Каждого Н Из Сорт Цикл
		Результат.Добавить(Н.Строка);
	КонецЦикла;

	Возврат Результат;

КонецФункции

&НаСервере
Функция КлючСклада(Склад)

	Возврат ?(ЗначениеЗаполнено(Склад), Строка(Склад.УникальныйИдентификатор()), "");

КонецФункции

&НаСервере
Функция КлючРасхода(Номенклатура, Склад)

	Возврат Строка(Номенклатура.УникальныйИдентификатор()) + "|" + КлючСклада(Склад);

КонецФункции

&НаСервере
Функция КлючОстатка(Номенклатура, Склад, ДатаОстатков)

	Возврат Строка(Номенклатура.УникальныйИдентификатор()) + "|"
		+ КлючСклада(Склад) + "|"
		+ Формат(ДатаОстатков, "ДФ=yyyyMMddHHmmss");

КонецФункции

&НаСервере
Функция ЗначениеСтроки(Структура, ИмяПоля, ИмяПоляАльт = "")

	Значение = "";
	Если Структура.Свойство(ИмяПоля) И ЗначениеЗаполнено(Структура[ИмяПоля]) Тогда
		Значение = СокрЛП(Строка(Структура[ИмяПоля]));
	ИначеЕсли ЗначениеЗаполнено(ИмяПоляАльт)
		И Структура.Свойство(ИмяПоляАльт) И ЗначениеЗаполнено(Структура[ИмяПоляАльт]) Тогда
		Значение = СокрЛП(Строка(Структура[ИмяПоляАльт]));
	КонецЕсли;

	Возврат Значение;

КонецФункции

&НаСервере
Функция ЧисловоеЗначение(Структура, ИмяПоля)

	Если НЕ Структура.Свойство(ИмяПоля) Тогда
		Возврат 0;
	КонецЕсли;

	Попытка
		Возврат Число(Структура[ИмяПоля]);
	Исключение
		Возврат 0;
	КонецПопытки;

КонецФункции

&НаСервере
Функция ДатаДокументаСтроки(СтрокаДокумента)

	Если СтрокаДокумента.Свойство("ДатаДокумента") Тогда
		Попытка
			Возврат Дата(СтрокаДокумента.ДатаДокумента);
		Исключение
		КонецПопытки;
	КонецЕсли;

	Возврат ТекущаяДатаСеанса();

КонецФункции

&НаСервере
Функция СкладБППоСкладуЗПЧ(СкладЗПЧ)

	СтрокиСклада = СоответствиеСкладовЧеки.НайтиСтроки(Новый Структура("СкладЗПЧ", СкладЗПЧ));
	Если СтрокиСклада.Количество() > 0 Тогда
		Возврат СтрокиСклада[0].СкладБП;
	КонецЕсли;

	Возврат Справочники.Склады.ПустаяСсылка();

КонецФункции

////////////////////////////////////////////////////////////////////////////////
// НИЖЕ — уже есть в Автомастере. При переносе в Автомастер эти две процедуры
// НЕ добавлять (будет дубль имени). В Автоточку — добавить.
////////////////////////////////////////////////////////////////////////////////

&НаСервере
Функция СчетаУчетаТоваров()

	Счета = Новый Массив;

	Для Каждого КодСчета Из СтрРазделить("41.01,41.02,41.11", ",") Цикл
		Счет = ПланыСчетов.Хозрасчетный.НайтиПоКоду(КодСчета);
		Если ЗначениеЗаполнено(Счет) Тогда
			Счета.Добавить(Счет);
		КонецЕсли;
	КонецЦикла;

	Возврат Счета;

КонецФункции

&НаСервере
Процедура ВывестиПроблемыПодбора(Проблемы)

	Если Проблемы.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;

	ОбщегоНазначения.СообщитьПользователю(
		СтрШаблон("Строк с недостаточными остатками: %1. Документы созданы, но не проведутся:",
			Проблемы.Количество()));

	Для Каждого Проблема Из Проблемы Цикл
		ОбщегоНазначения.СообщитьПользователю(
			СтрШаблон("  %1 / %2 (арт. %3): требуется %4, доступно %5 — %6",
				Строка(Проблема.Склад),
				Проблема.Наименование,
				?(ЗначениеЗаполнено(Проблема.Артикул), Проблема.Артикул, "—"),
				Строка(Проблема.Требуется),
				Строка(Проблема.Доступно),
				Проблема.Причина));
	КонецЦикла;

КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
//
//                     ЧАСТЬ III. КАК ВСТРАИВАТЬ
//
////////////////////////////////////////////////////////////////////////////////
//
// ОБЩЕЕ ДЛЯ ОБЕИХ ОБРАБОТОК
//
// Удалить (заменены кодом выше):
//     СопоставитьНоменклатуруЧека
//     НайтиНоменклатуруПоНаименованиюРасширенный
//     НайтиНоменклатуруПоАртикулуСОстатками
//     НайтиНоменклатуруПоНаименованию      (мёртвый код, нигде не вызывается)
//     ПолучитьОстатокНоменклатуры
//
// Не трогать: легаси-чтение DBF из C:\SERVICE1\Obmen\Выгрузки
// (ЗагрузитьЧекиНаСервере, НайтиНоменклатуру, СопоставитьСклад) — этот канал
// не используется, база ЗПЧ работает по HTTP.
//
//------------------------------------------------------------------------------
// АВТОМАСТЕР
//------------------------------------------------------------------------------
//
// Дополнительно удалить (весь механизм КонтекстПодбора заменён пакетным):
//     НовыйКонтекстПодбора
//     КлючРезерва
//     ДоступныйОстаток
//     ЗарезервироватьОстаток
//     ЗарегистрироватьПроблемуПодбора
//
// НЕ добавлять из этого файла: СчетаУчетаТоваров, ВывестиПроблемыПодбора —
// они уже есть.
//
// В СоздатьДокументыОРП (около строки 1483):
//
//     было:
//         Израсходовано   = Новый Соответствие;
//         ПроблемыПодбора = Новый Массив;
//
//     стало:
//         ПроблемыПодбора = ПодобратьНоменклатуруПакета(МассивДокументов, Ложь);
//
//     Важно: вызов должен идти ПОСЛЕ формирования ЗаполненныеСклады, то есть
//     после того как таблица СоответствиеСкладовЧеки подтверждена.
//
// В теле цикла по документам удалить (около строк 1528-1529):
//         КонтекстПодбора = НовыйКонтекстПодбора(
//             СкладБП, ДатаДокумента, Израсходовано, ПроблемыПодбора);
//
// В цикле по товарам удалить (около строки 1561):
//         СопоставитьНоменклатуруЧека(СтрокаТовара, КонтекстПодбора);
//     Чтение СтрокаТовара.НоменклатураБП ниже остаётся без изменений.
//
// Вызов ВывестиПроблемыПодбора(ПроблемыПодбора) в конце процедуры остаётся.
//
//------------------------------------------------------------------------------
// АВТОТОЧКА
//------------------------------------------------------------------------------
//
// 1. Сократить СоответствиеДанныхИБЧеки до раскладки складов — как в Автомастере:
//
//        &НаСервере
//        Процедура СоответствиеДанныхИБЧеки(ДанныеДокументов)
//            // Здесь только раскладка складов ЗПЧ по складам БП. Номенклатура
//            // подбирается в СоздатьДокументыОРП: до подтверждения таблицы
//            // соответствия склад неизвестен, а остатки проверяются по складу документа.
//            Для Каждого Строка Из ДанныеДокументов Цикл
//                СоответствиеСкладовЧекиОбработка(Строка);
//            КонецЦикла;
//        КонецПроцедуры
//
// 2. В начало СоздатьДокументыОРП (около строки 1476), после того как построен
//    список сопоставленных складов:
//
//        ПроблемыПодбора = ПодобратьНоменклатуруПакета(МассивДокументов, Ложь);
//
// 3. В цикле по товарам читать СтрокаТовара.НоменклатураБП вместо вызова
//    СопоставитьНоменклатуруЧека.
//
// 4. В конец СоздатьДокументыОРП:
//
//        ВывестиПроблемыПодбора(ПроблемыПодбора);
//
// 5. Добавить из этого файла СчетаУчетаТоваров и ВывестиПроблемыПодбора —
//    в Автоточке их нет.
//
//------------------------------------------------------------------------------
// ПРОВЕРКА ПЕРЕД БОЕВЫМ ВКЛЮЧЕНИЕМ
//------------------------------------------------------------------------------
//
// Ничего из этого файла не проверено на данных: доступа к информационной базе
// не было, синтаксический контроль не выполнялся. Обязательный минимум:
//
//   1cv8 DESIGNER /F <база> /CheckModules
//
// Затем прогон с ВыводитьДиагностику = Истина (второй параметр
// ПодобратьНоменклатуруПакета) на эталонном документе ООБП-000452 от 05.06.2026.
// Ожидаемый результат по строкам 48/49:
//
//   [Артикул/ранг] "Р/к поршня переднего суппорта 14"..." (цена 1730,00)
//       -> "Р/к поршня переднего суппорта 14"..." (себест. 1060,36)
//   [Артикул/ранг] "Р/к поршня переднего суппорта 14"..." (цена 1610,00)
//       -> "Frenkit 252909 ремкомплект дискового тормоза с поршнем" (себест. 946,00)
//
// Пустых НоменклатураБП — 0 из 88, документ проводится.
//
// Отдельно посмотреть в диагностике:
//   * сколько строк ушло в фазу «Наименование» — много означает расхождение
//     артикулов справочника с тем, что шлёт ЗПЧ (см. вывод 5, обрезка до 25);
//   * что получилось по группе «Колодки тормозные передние» — полтора десятка
//     карточек с одним наименованием и разными артикулами;
//   * строки с проходом «добор» — там ранговое соответствие не было
//     однозначным, себестоимость могла лечь не на ту карточку.
//
////////////////////////////////////////////////////////////////////////////////