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


//++ Варнин А.А. - Задача №68771
// Пакетная версия подбора номенклатуры.
// Было: 1 запрос (полное сканирование Номенклатуры + виртуальная таблица остатков)
//       на КАЖДУЮ строку КАЖДОГО чека.
// Стало: 1 запрос кандидатов на всю загрузку + 1 запрос остатков на каждую
//       уникальную дату чека. Метаданные (счёт 41.01, индекс субконто "Склады")
//       читаются один раз.
// Логика подбора, приоритеты, накопитель свободных остатков и разбиение строк
// не изменены.

#Область ПрограммныйИнтерфейс

Функция НоваяТаблицаСоответствияСкладов() Экспорт

	Таблица = Новый ТаблицаЗначений;
	Таблица.Колонки.Добавить("СкладЗПЧ");
	Таблица.Колонки.Добавить("СкладБП");
	Возврат Таблица;

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

Функция ОбработатьЧекиВФоне(ПараметрыЗагрузки) Экспорт

	Результат = Новый Структура;
	Результат.Вставить("Лог",                НоваяТаблицаЛога());
	Результат.Вставить("СозданныеДокументы", Новый Массив);
	Результат.Вставить("ОбработаноЧеков",    0);
	Результат.Вставить("ЕстьОшибки",         Ложь);

	ДанныеДокументов = ПараметрыЗагрузки.Данные;

	ЗаполнитьНоменклатуруЧеков(ДанныеДокументов, ПараметрыЗагрузки, Результат.Лог);

	Результат.ОбработаноЧеков = ДанныеДокументов.Количество();

	ДлительныеОперации.СообщитьПрогресс(100, НСтр("ru = 'Готово'"));

	Для Каждого СтрокаЛога Из Результат.Лог Цикл
		Если СтрокаЛога.ЭтоОшибка Тогда
			Результат.ЕстьОшибки = Истина;
			Прервать;
		КонецЕсли;
	КонецЦикла;

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

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

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

#Область ОсновнойАлгоритм

Процедура ЗаполнитьНоменклатуруЧеков(ДанныеДокументов, ПараметрыЗагрузки, Лог)

	СоответствиеСкладов = ПараметрыЗагрузки.СоответствиеСкладов;

	Документы = ДокументыПоВозрастаниюДаты(ДанныеДокументов);

	ДлительныеОперации.СообщитьПрогресс(5, НСтр("ru = 'Подготовка данных'"));

	Подготовка = СобратьКлючиПоиска(Документы, СоответствиеСкладов);

	// Метаданные читаем один раз на всю загрузку, а не на каждую строку.
	ИндексСклада = ИндексСубконтоСклады();
	СчетТоваров  = ПланыСчетов.Хозрасчетный.НайтиПоКоду("41.01");

	МенеджерВТ = Новый МенеджерВременныхТаблиц;

	ДлительныеОперации.СообщитьПрогресс(15, НСтр("ru = 'Подбор кандидатов'"));
	КэшКандидатов = ПостроитьКэшКандидатов(Подготовка, МенеджерВТ, Лог);

	ДлительныеОперации.СообщитьПрогресс(35, НСтр("ru = 'Получение остатков'"));
	КэшОстатков = ПостроитьКэшОстатков(Подготовка.Даты, МенеджерВТ, СчетТоваров, ИндексСклада, Лог);

	МенеджерВТ.Закрыть();

	СвободныеОстатки = Новый Соответствие;
	КэшСкладов       = Новый Соответствие;

	ВсегоДокументов = Документы.Количество();
	НомерДокумента  = 0;

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

		НомерДокумента = НомерДокумента + 1;

		ДлительныеОперации.СообщитьПрогресс(
			40 + Окр(55 * НомерДокумента / ?(ВсегоДокументов = 0, 1, ВсегоДокументов)),
			СтрШаблон(НСтр("ru = 'Подбор номенклатуры: чек %1 из %2'"),
				НомерДокумента, ВсегоДокументов));

		СкладБП = КэшСкладов.Получить(Строка(СтрокаДокумента.Склад));
		Если СкладБП = Неопределено Тогда
			СкладБП = СкладБППоСкладуЗПЧ(СтрокаДокумента.Склад, СоответствиеСкладов);
			КэшСкладов.Вставить(Строка(СтрокаДокумента.Склад), СкладБП);
		КонецЕсли;

		ПрименятьСклад = (ИндексСклада > 0) И ЗначениеЗаполнено(СкладБП);
		ДатаОстатков   = КонецДня(Дата(СтрокаДокумента.ДатаДокумента));
		ОстаткиНаДату  = КэшОстатков.Получить(ДатаОстатков);

		НовыеТовары = Новый Массив;

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

			Требуется = СтрокаТовара.Количество;

			Кандидаты = КандидатыНоменклатуры(СтрокаТовара, КэшКандидатов);
			ЗаполнитьПриоритетыКандидатов(Кандидаты, СтрокаТовара);
			ПодставитьСвободныеОстатки(Кандидаты, СвободныеОстатки, ОстаткиНаДату,
				СкладБП, ПрименятьСклад);

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

			Если Кандидаты.Количество() = 0 Тогда
				ДобавитьВЛог(Лог, СтрокаТовара.Номенклатура,
					СтрШаблон(НСтр("ru = 'Не найдена номенклатура БП (кат. номер %1)'"),
						СтрокаТовара.КаталожныйНомерНоменклатуры), Истина);
				ЧастьСтроки = КопияСтрокиТовара(СтрокаТовара, Требуется);
				ЧастьСтроки.Вставить("НоменклатураБП", Справочники.Номенклатура.ПустаяСсылка());
				НовыеТовары.Добавить(ЧастьСтроки);
				Продолжить;
			КонецЕсли;

			ОбщийСвободный = Кандидаты.Итог("Остаток");
			Если ОбщийСвободный < Требуется Тогда
				ДобавитьВЛог(Лог, СтрокаТовара.Номенклатура,
					СтрШаблон(НСтр("ru = 'Запрашиваемое количество %1 превышает свободный остаток %2'"),
						Требуется, ОбщийСвободный), Истина);
			КонецЕсли;

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

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

			ОсталосьРаспределить = Требуется;

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

				Если ОсталосьРаспределить <= 0 Тогда
					Прервать;
				КонецЕсли;

				Если Кандидат.Остаток <= 0 Тогда
					Продолжить;
				КонецЕсли;

				Взято = ?(Кандидат.Остаток < ОсталосьРаспределить, Кандидат.Остаток, ОсталосьРаспределить);

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

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

				ДобавитьВЛог(Лог, СтрокаТовара.Номенклатура,
					СтрШаблон(НСтр("ru = 'Строка разбита: %1 шт на карточку ""%2""'"),
						Взято, Кандидат.Наименование), Ложь);
			КонецЦикла;

			Если ОсталосьРаспределить > 0 Тогда
				ЧастьСтроки = КопияСтрокиТовара(СтрокаТовара, ОсталосьРаспределить);
				ЧастьСтроки.Вставить("НоменклатураБП", Кандидаты[0].Номенклатура);
				НовыеТовары.Добавить(ЧастьСтроки);
				СписатьОстаток(СвободныеОстатки, СкладБП, Кандидаты[0].Номенклатура, ОсталосьРаспределить);
			КонецЕсли;

		КонецЦикла;

		СтрокаДокумента.Вставить("Товары", НовыеТовары);

	КонецЦикла;

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

#КонецОбласти

#Область ПодготовкаДанных

// Один проход по всем чекам: заполняет соответствие складов и собирает
// уникальные ключи поиска и уникальные даты остатков.
Функция СобратьКлючиПоиска(Документы, СоответствиеСкладов)

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

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

		СоответствиеСкладовЧекиОбработка(СтрокаДокумента, СоответствиеСкладов);

		Даты.Вставить(КонецДня(Дата(СтрокаДокумента.ДатаДокумента)), Истина);

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

			Ключ = КлючПоиска(СтрокаТовара.КаталожныйНомерНоменклатуры);
			Если НЕ ПустаяСтрока(Ключ) Тогда
				Артикулы.Вставить(Ключ, Истина);
			КонецЕсли;

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

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

		КонецЦикла;

	КонецЦикла;

	Результат = Новый Структура;
	Результат.Вставить("ТаблицаАртикулов",     ТаблицаКлючей(Артикулы, 100));
	Результат.Вставить("ТаблицаНаименований",  ТаблицаКлючей(Наименования, 250));
	Результат.Вставить("Даты",                 МассивКлючей(Даты));

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

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

Функция ТаблицаКлючей(Ключи, ДлинаСтроки)

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

	Для Каждого КлючИЗначение Из Ключи Цикл
		Таблица.Добавить().Ключ = Лев(КлючИЗначение.Ключ, ДлинаСтроки);
	КонецЦикла;

	Возврат Таблица;

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

Функция МассивКлючей(Соответствие)

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

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

Функция КлючПоиска(Значение)

	Возврат ВРег(СокрЛП(Строка(Значение)));

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

#КонецОбласти

#Область Кэши

// Один запрос на всю загрузку.
// Вместо "Артикул = X ИЛИ Наименование = Y" (полное сканирование справочника)
// — соединение с временной таблицей ключей по каждому реквизиту отдельно,
// каждая ветка идёт по своему индексу.
// ВАЖНО: для реквизита "Артикул" справочника Номенклатура в конфигураторе
// должно быть установлено свойство Индексировать = Индексировать.
Функция ПостроитьКэшКандидатов(Подготовка, МенеджерВТ, Лог)

	Кэш = Новый Соответствие;

	ЕстьАртикулы    = (Подготовка.ТаблицаАртикулов.Количество() > 0);
	ЕстьНаименования = (Подготовка.ТаблицаНаименований.Количество() > 0);

	Если НЕ ЕстьАртикулы И НЕ ЕстьНаименования Тогда
		Возврат Кэш;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	Т.Ключ КАК Ключ
	|ПОМЕСТИТЬ ВТАртикулы
	|ИЗ
	|	&ТаблицаАртикулов КАК Т
	|ИНДЕКСИРОВАТЬ ПО
	|	Ключ
	|;
	|////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Т.Ключ КАК Ключ
	|ПОМЕСТИТЬ ВТНаименования
	|ИЗ
	|	&ТаблицаНаименований КАК Т
	|ИНДЕКСИРОВАТЬ ПО
	|	Ключ
	|;
	|////////////////////////////////////////////////////////////
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	А.Ключ           КАК Ключ,
	|	Ном.Ссылка       КАК Номенклатура,
	|	Ном.Наименование КАК Наименование
	|ПОМЕСТИТЬ ВТСвязи
	|ИЗ
	|	ВТАртикулы КАК А
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Ном
	|		ПО Ном.Артикул = А.Ключ
	|ГДЕ
	|	НЕ Ном.ПометкаУдаления
	|	И НЕ Ном.ЭтоГруппа
	|
	|ОБЪЕДИНИТЬ
	|
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	Н.Ключ,
	|	Ном.Ссылка,
	|	Ном.Наименование
	|ИЗ
	|	ВТНаименования КАК Н
	|		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Ном
	|		ПО Ном.Наименование = Н.Ключ
	|ГДЕ
	|	НЕ Ном.ПометкаУдаления
	|	И НЕ Ном.ЭтоГруппа
	|;
	|////////////////////////////////////////////////////////////
	|ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	Св.Номенклатура КАК Номенклатура
	|ПОМЕСТИТЬ ВТКандидаты
	|ИЗ
	|	ВТСвязи КАК Св
	|ИНДЕКСИРОВАТЬ ПО
	|	Номенклатура
	|;
	|////////////////////////////////////////////////////////////
	|ВЫБРАТЬ
	|	Св.Ключ          КАК Ключ,
	|	Св.Номенклатура  КАК Номенклатура,
	|	Св.Наименование  КАК Наименование
	|ИЗ
	|	ВТСвязи КАК Св";

	Запрос.УстановитьПараметр("ТаблицаАртикулов",    Подготовка.ТаблицаАртикулов);
	Запрос.УстановитьПараметр("ТаблицаНаименований", Подготовка.ТаблицаНаименований);

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

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

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

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

	КонецЦикла;

	Возврат Кэш;

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

// Один запрос остатков на каждую уникальную дату чека (обычно 1-2 на загрузку)
// вместо одного запроса на строку товара.
Функция ПостроитьКэшОстатков(Даты, МенеджерВТ, СчетТоваров, ИндексСклада, Лог)

	Кэш = Новый Соответствие;

	Если НЕ ЗначениеЗаполнено(СчетТоваров) Тогда
		ДобавитьВЛог(Лог, "", НСтр("ru = 'Не найден счёт 41.01'"), Истина);
		Возврат Кэш;
	КонецЕсли;

	ПолеСклада        = ?(ИндексСклада > 0, СтрШаблон("Ост.Субконто%1 КАК Склад,", Строка(ИндексСклада)), "");
	ГруппировкаСклада = ?(ИндексСклада > 0, СтрШаблон(",%1	Ост.Субконто%2", Символы.ПС, Строка(ИндексСклада)), "");

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

	Запрос = Новый Запрос;
	Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
	Запрос.Текст = ТекстЗапроса;
	Запрос.УстановитьПараметр("Счет", СчетТоваров);

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

		Запрос.УстановитьПараметр("ДатаОстатков", ДатаОстатков);

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

		ПоСкладу = Новый Соответствие;
		Итого    = Новый Соответствие;

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

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

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

			Текущий = Итого.Получить(КлючНоменклатуры);
			Итого.Вставить(КлючНоменклатуры, ?(Текущий = Неопределено, 0, Текущий) + Выборка.Остаток);

		КонецЦикла;

		Кэш.Вставить(ДатаОстатков, Новый Структура("ПоСкладу, Итого", ПоСкладу, Итого));

	КонецЦикла;

	Возврат Кэш;

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

#КонецОбласти

#Область ПодборНоменклатуры

// Кандидаты собираются из кэша по трём ключам строки, запросов к базе нет.
Функция КандидатыНоменклатуры(СтрокаТовара, КэшКандидатов)

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

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

	ДобавитьКандидатовПоКлючу(Результат, Добавленные, КэшКандидатов,
		КлючПоиска(СтрокаТовара.КаталожныйНомерНоменклатуры));

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

	Если СтрокаТовара.Свойство("НоменклатураПоставщика")
		И ЗначениеЗаполнено(СтрокаТовара.НоменклатураПоставщика) Тогда
		ДобавитьКандидатовПоКлючу(Результат, Добавленные, КэшКандидатов,
			КлючПоиска(СтрокаТовара.НоменклатураПоставщика));
	КонецЕсли;

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

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

Процедура ДобавитьКандидатовПоКлючу(Результат, Добавленные, КэшКандидатов, Ключ)

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

	Список = КэшКандидатов.Получить(Ключ);
	Если Список = Неопределено Тогда
		Возврат;
	КонецЕсли;

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

		КлючКандидата = Строка(Кандидат.Номенклатура.УникальныйИдентификатор());
		Если Добавленные.Получить(КлючКандидата) <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		Добавленные.Вставить(КлючКандидата, Истина);

		Стр = Результат.Добавить();
		Стр.Номенклатура = Кандидат.Номенклатура;
		Стр.Наименование = Кандидат.Наименование;

	КонецЦикла;

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

Процедура ЗаполнитьПриоритетыКандидатов(Кандидаты, СтрокаТовара)

	НаимЧека = ВРег(СокрЛП(Строка(СтрокаТовара.Номенклатура)));

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

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

		НаимКандидата = ВРег(СокрЛП(Кандидат.Наименование));

		Если НаимКандидата = НаимЧека Тогда
			Кандидат.Приоритет = 0;
		ИначеЕсли НЕ ПустаяСтрока(НаимПоставщика)
			И (СтрНайти(НаимПоставщика, НаимКандидата) > 0
				ИЛИ СтрНайти(НаимКандидата, НаимПоставщика) > 0) Тогда
			Кандидат.Приоритет = 1;
		Иначе
			Кандидат.Приоритет = 2;
		КонецЕсли;

	КонецЦикла;

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

// Первый раз остаток берётся из кэша (срез на дату чека),
// далее — только из накопителя списаний.
Процедура ПодставитьСвободныеОстатки(Кандидаты, СвободныеОстатки, ОстаткиНаДату, Склад, ПрименятьСклад)

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

		Ключ = КлючОстатка(Склад, Кандидат.Номенклатура);
		Сохранённый = СвободныеОстатки.Получить(Ключ);

		Если Сохранённый <> Неопределено Тогда
			Кандидат.Остаток = Сохранённый;
			Продолжить;
		КонецЕсли;

		Кандидат.Остаток = ОстатокИзКэша(ОстаткиНаДату, Ключ, Кандидат.Номенклатура, ПрименятьСклад);
		СвободныеОстатки.Вставить(Ключ, Кандидат.Остаток);

	КонецЦикла;

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

Функция ОстатокИзКэша(ОстаткиНаДату, Ключ, Номенклатура, ПрименятьСклад)

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

	Если ПрименятьСклад Тогда
		Значение = ОстаткиНаДату.ПоСкладу.Получить(Ключ);
	Иначе
		Значение = ОстаткиНаДату.Итого.Получить(Строка(Номенклатура.УникальныйИдентификатор()));
	КонецЕсли;

	Возврат ?(Значение = Неопределено, 0, Значение);

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

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

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

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

Функция КопияСтрокиТовара(СтрокаТовара, Количество)

	Копия = Новый Структура;
	Для Каждого КлючИЗначение Из СтрокаТовара Цикл
		Копия.Вставить(КлючИЗначение.Ключ, КлючИЗначение.Значение);
	КонецЦикла;

	ИсходноеКоличество = СтрокаТовара.Количество;
	Копия.Вставить("Количество", Количество);

	Если ИсходноеКоличество = 0 Тогда
		Возврат Копия;
	КонецЕсли;

	Доля = Количество / ИсходноеКоличество;

	Если Копия.Свойство("Сумма") Тогда
		Копия.Вставить("Сумма", Окр(СтрокаТовара.Сумма * Доля, 2));
	КонецЕсли;
	Если Копия.Свойство("СуммаНДС") Тогда
		Копия.Вставить("СуммаНДС", Окр(СтрокаТовара.СуммаНДС * Доля, 2));
	КонецЕсли;

	Возврат Копия;

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

Функция ДокументыПоВозрастаниюДаты(ДанныеДокументов)

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

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

	Таблица.Сортировать("Дата Возр");

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

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

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

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

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

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

#КонецОбласти

#Область Склады

Процедура СоответствиеСкладовЧекиОбработка(Строка, СоответствиеСкладовЧеки)

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

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

Функция ПодобратьСклад(СкладЗПЧ)

	СкладВБазе = Справочники.Склады.НайтиПоНаименованию(СкладЗПЧ, Ложь);

	Если ЗначениеЗаполнено(СкладВБазе) Тогда
		Возврат СкладВБазе;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
		"ВЫБРАТЬ ПЕРВЫЕ 1
		|	Склады.Ссылка КАК Ссылка
		|ИЗ
		|	Справочник.Склады КАК Склады
		|ГДЕ
		|	Склады.Наименование ПОДОБНО ""%"" + &Наименование + ""%"" ";

	Запрос.УстановитьПараметр("Наименование", СкладЗПЧ);

	Выборка = Запрос.Выполнить().Выбрать();

	Если Выборка.Следующий() Тогда
		СкладВБазе = Выборка.Ссылка;
	КонецЕсли;

	Возврат СкладВБазе;

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

Функция ИндексСубконтоСклады()

	Счет = ПланыСчетов.Хозрасчетный.НайтиПоКоду("41.01");
	Если НЕ ЗначениеЗаполнено(Счет) Тогда
		Возврат 0;
	КонецЕсли;

	ВидСклады = ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Склады;

	Индекс = 0;
	Для Каждого СтрокаСубконто Из Счет.ВидыСубконто Цикл
		Индекс = Индекс + 1;
		Если СтрокаСубконто.ВидСубконто = ВидСклады Тогда
			Возврат Индекс;
		КонецЕсли;
	КонецЦикла;

	Возврат 0;

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

Функция СкладБППоСкладуЗПЧ(СкладЗПЧ, СоответствиеСкладов)

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

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

#КонецОбласти

#Область Лог

Функция НоваяТаблицаЛога()

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

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

Процедура ДобавитьВЛог(Лог, Номенклатура = "", Текст = "", ЭтоОшибка = Ложь)

	Стр = Лог.Добавить();
	Стр.Номенклатура = Строка(Номенклатура);
	Стр.Текст        = Текст;
	Стр.ЭтоОшибка    = ЭтоОшибка;

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

#КонецОбласти

#КонецОбласти