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


////////////////////
// ЧАСТЬ 1. Общий модуль РаботаСФормой (флаги: Сервер, Вызов сервера — НЕ нужен)
//
// Точка входа для фонового задания — ЗагрузитьЧеки(ПараметрыЗагрузки).
// Все сообщения пользователю заменены на таблицу-лог, которая возвращается
// в результате и выводится на форме.
////////////////////////////////////////////////////////////////////////////////

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

// Конструктор параметров загрузки. Заполняется на форме, целиком уезжает в фон.
//
// Возвращаемое значение:
//  Структура:
//   * ДатаНачала            - Дата
//   * ДатаОкончания         - Дата
//   * Организация           - СправочникСсылка.Организации
//   * ИНН                   - Строка
//   * НовыйАлгоритм         - Булево
//   * СоответствиеСкладов   - ТаблицаЗначений - колонки СкладЗПЧ, СкладБП
//   * ТолькоПодбор          - Булево - Истина: только подобрать номенклатуру,
//                             документы не создавать (режим проверки)
//
Функция ПараметрыЗагрузкиЧеков() Экспорт

	Параметры = Новый Структура;
	Параметры.Вставить("ДатаНачала",          '00010101');
	Параметры.Вставить("ДатаОкончания",       '00010101');
	Параметры.Вставить("Организация",         Справочники.Организации.ПустаяСсылка());
	Параметры.Вставить("ИНН",                 "");
	Параметры.Вставить("НовыйАлгоритм",       Истина);
	Параметры.Вставить("СоответствиеСкладов", НоваяТаблицаСоответствияСкладов());
	Параметры.Вставить("ТолькоПодбор",        Ложь);

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

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

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

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

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

// Точка входа фонового задания.
//
// Параметры:
//  ПараметрыЗагрузки - см. ПараметрыЗагрузкиЧеков
//
// Возвращаемое значение:
//  Структура:
//   * Лог                 - ТаблицаЗначений
//   * СозданныеДокументы  - Массив из ДокументСсылка.ОтчетОРозничныхПродажах
//   * ОбработаноЧеков     - Число
//   * ЕстьОшибки          - Булево
//
Функция ЗагрузитьЧеки(ПараметрыЗагрузки) Экспорт

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

	// --- Этап 1: получение данных из ЗПЧ ------------------------------------
	ДлительныеОперации.СообщитьПрогресс(1, НСтр("ru = 'Получение данных из ЗПЧ...'"));

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

	Если ДанныеДокументов.Количество() = 0 Тогда
		ДобавитьВЛог(Результат.Лог, , , НСтр("ru = 'За указанный период чеков не получено'"), Ложь);
		Возврат Результат;
	КонецЕсли;

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

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

	// --- Этап 3: создание документов ----------------------------------------
	Если НЕ ПараметрыЗагрузки.ТолькоПодбор Тогда
		СоздатьДокументыПоЧекам(ДанныеДокументов, ПараметрыЗагрузки, Результат);
	КонецЕсли;

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

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

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

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

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

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

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

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

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

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

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

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

////////////////////////////////////////////////////////////////////////////////
// Подбор номенклатуры

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

	// Ключ: "GUID склада|GUID номенклатуры", значение: количество,
	// уже разобранное строками текущего сеанса загрузки.
	Потрачено = Новый Соответствие;

	// Документы разбираем в хронологическом порядке: расход более позднего
	// чека не должен вычитаться из остатка более раннего.
	ДанныеДокументов = ДокументыПоВозрастаниюДаты(ДанныеДокументов);

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

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

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

		// Прогресс отдаём по документам, а не по строкам: на 300 строк
		// это сотни лишних записей во временное хранилище.
		ДлительныеОперации.СообщитьПрогресс(
			5 + Окр(50 * НомерДокумента / ВсегоДокументов),
			СтрШаблон(НСтр("ru = 'Подбор номенклатуры: чек %1 из %2'"),
				НомерДокумента, ВсегоДокументов));

		ПредставлениеЧека = Строка(СтрокаДокумента.НомерДокумента);

		СкладБП = СкладБППоСкладуЗПЧ(СтрокаДокумента.Склад, ПараметрыЗагрузки.СоответствиеСкладов);

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

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

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

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

			Кандидаты = КандидатыНоменклатуры(СтрокаТовара, СкладБП, ДатаОстатков, Лог, ПредставлениеЧека);
			ЗаполнитьПриоритетыКандидатов(Кандидаты, СтрокаТовара);
			ПрименитьРанееПотраченное(Кандидаты, Потрачено, СкладБП);

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

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

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

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

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

			// 2) Одной карточкой не закрывается — разбиваем строку по кандидатам.
			ОсталосьРаспределить = Требуется;

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

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

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

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

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

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

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

			КонецЦикла;

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

		КонецЦикла;

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

	КонецЦикла;

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

// Сортировка входящих документов по дате по возрастанию.
Функция ДокументыПоВозрастаниюДаты(ДанныеДокументов)

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

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

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

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

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

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

// Приоритет: 0 — точное совпадение с наименованием чека,
//            1 — пересечение с наименованием поставщика, 2 — прочее.
Процедура ЗаполнитьПриоритетыКандидатов(Кандидаты, СтрокаТовара)

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

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

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

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

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

	КонецЦикла;

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

// Уменьшает остаток кандидата на количество, уже разобранное
// предыдущими строками текущего сеанса загрузки.
Процедура ПрименитьРанееПотраченное(Кандидаты, Потрачено, Склад)

	Для Каждого Кандидат Из Кандидаты Цикл
		Расход = Потрачено.Получить(КлючОстатка(Склад, Кандидат.Номенклатура));
		Если Расход <> Неопределено Тогда
			Кандидат.Остаток = Кандидат.Остаток - Расход;
		КонецЕсли;
	КонецЦикла;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Функция КандидатыНоменклатуры(СтрокаТовара, Склад, ДатаОстатков, Лог, ПредставлениеЧека)

	КатНомер     = Лев(СокрЛП(Строка(СтрокаТовара.КаталожныйНомерНоменклатуры)), 25);
	Наименование = СокрЛП(Строка(СтрокаТовара.Номенклатура));

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

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

	ИндексСклада   = ИндексСубконтоСклады();
	ПрименятьСклад = (ИндексСклада > 0) И ЗначениеЗаполнено(Склад);
	УсловиеСклада  = ?(ПрименятьСклад,
		СтрШаблон(" И Субконто%1 = &Склад", Строка(ИндексСклада)), "");

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

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

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

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

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

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

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

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

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

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

	Возврат 0;

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

// Соответствие складов теперь приходит параметром, а не читается из реквизита формы.
Функция СкладБППоСкладуЗПЧ(СкладЗПЧ, СоответствиеСкладов)

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

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

////////////////////////////////////////////////////////////////////////////////
// Заглушки под существующий код — подставьте свои реализации.

Функция ПолучитьЧекиИзЗПЧ(ПараметрыЗагрузки)

	// ЗДЕСЬ: перенесённый HTTP-запрос к ЗПЧ + разбор JSON.
	// Возвращает массив структур документов (как раньше — ДанныеДокументов).
	ВызватьИсключение НСтр("ru = 'Не реализовано: перенесите сюда получение чеков по HTTP'");

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

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

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

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

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

		// Каждый документ — отдельная транзакция. Не заворачивать весь цикл
		// в одну: в файловой базе это заблокирует базу на всё время загрузки.
		Попытка
			// ЗДЕСЬ: создание и проведение ОРП по СтрокаДокумента.
			// Результат.СозданныеДокументы.Добавить(ДокументСсылка);
		Исключение
			ДобавитьВЛог(Результат.Лог, СтрокаДокумента.НомерДокумента, ,
				ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()), Истина);
		КонецПопытки;

	КонецЦикла;

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

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


////////////////////////////////////////////////////////////////////////////////
// ЧАСТЬ 2. Модуль формы «Форма настроек загрузки из ЗПЧ»
//
// Реквизиты формы, которые надо добавить:
//   ИдентификаторЗадания   - УникальныйИдентификатор
//   АдресРезультата        - Строка
//   ПроцентВыполнения      - Число (0..100)
//   ТекстВыполнения        - Строка
//   ЛогЗагрузки            - ТаблицаЗначений (Чек, Номенклатура, Текст, ЭтоОшибка)
//
// Элементы формы:
//   ГруппаПрогресс (Группа, обычная, Видимость = Ложь)
//     Индикатор       — поле с типом «Индикатор», данные = ПроцентВыполнения
//     НадписьПрогресс — поле надписи, данные = ТекстВыполнения
//     ОтменитьЗагрузку — кнопка
//   ЛогЗагрузки — таблица формы, у колонки Текст условное оформление по ЭтоОшибка
////////////////////////////////////////////////////////////////////////////////

&НаКлиенте
Процедура ЗагрузитьЧеки(Команда)

	Если ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
		ПоказатьПредупреждение(, НСтр("ru = 'Загрузка уже выполняется'"));
		Возврат;
	КонецЕсли;

	ДлительнаяОперация = НачатьЗагрузкуЧеков();

	ИдентификаторЗадания = ДлительнаяОперация.ИдентификаторЗадания;
	АдресРезультата      = ДлительнаяОперация.АдресРезультата;

	ПоказатьИндикатор(Истина, 0, НСтр("ru = 'Запуск...'"));

	ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
	// Своя индикация на форме, поэтому стандартное окно ожидания не выводим.
	ПараметрыОжидания.ВыводитьОкноОжидания          = Ложь;
	ПараметрыОжидания.ВыводитьПрогрессВыполнения    = Истина;
	ПараметрыОжидания.Интервал                      = 1;
	ПараметрыОжидания.ОповещениеОПрогрессеВыполнения =
		Новый ОписаниеОповещения("ЗагрузкаЧековПрогресс", ЭтотОбъект);

	ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация,
		Новый ОписаниеОповещения("ЗагрузкаЧековЗавершена", ЭтотОбъект), ПараметрыОжидания);

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

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

	ЛогЗагрузки.Очистить();

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

	// Реквизиты формы в фон не уезжают — таблицу соответствия копируем в параметры.
	ПараметрыЗагрузки.СоответствиеСкладов =
		РеквизитФормыВЗначение("СоответствиеСкладовЧеки");

	ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор);
	ПараметрыВыполнения.НаименованиеФоновогоЗадания = НСтр("ru = 'Загрузка чеков из ЗПЧ'");
	// Без этого в файловой базе и в режиме отладки задание выполнится
	// синхронно, и прогресса на форме не будет.
	ПараметрыВыполнения.ЗапуститьВФоне = Истина;

	Возврат ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения,
		"РаботаСФормой.ЗагрузитьЧеки", ПараметрыЗагрузки);

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

&НаКлиенте
Процедура ЗагрузкаЧековПрогресс(Состояние, ДополнительныеПараметры) Экспорт

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

	ПроцентВыполнения = Состояние.Прогресс.Процент;
	Если ЗначениеЗаполнено(Состояние.Прогресс.Текст) Тогда
		ТекстВыполнения = Состояние.Прогресс.Текст;
	КонецЕсли;

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

&НаКлиенте
Процедура ЗагрузкаЧековЗавершена(Результат, ДополнительныеПараметры) Экспорт

	ИдентификаторЗадания = Неопределено;
	ПоказатьИндикатор(Ложь);

	Если Результат = Неопределено Тогда
		// Задание отменено пользователем.
		Возврат;
	КонецЕсли;

	Если Результат.Статус = "Ошибка" Тогда
		СтандартныеПодсистемыКлиент.ВывестиИнформациюОбОшибке(Результат.ИнформацияОбОшибке);
		Возврат;
	КонецЕсли;

	Итоги = ОбработатьРезультатЗагрузки(Результат.АдресРезультата);

	Если Итоги.ЕстьОшибки Тогда
		ПоказатьПредупреждение(,
			НСтр("ru = 'Загрузка завершена с замечаниями. Подробности — в таблице ниже.'"));
	Иначе
		ПоказатьОповещениеПользователя(НСтр("ru = 'Загрузка чеков'"), ,
			СтрШаблон(НСтр("ru = 'Обработано чеков: %1'"), Итоги.ОбработаноЧеков));
	КонецЕсли;

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

&НаСервере
Функция ОбработатьРезультатЗагрузки(Адрес)

	Итоги = Новый Структура("ОбработаноЧеков, ЕстьОшибки", 0, Ложь);

	Результат = ПолучитьИзВременногоХранилища(Адрес);
	Если Результат = Неопределено Тогда
		Возврат Итоги;
	КонецЕсли;

	ЗначениеВРеквизитФормы(Результат.Лог, "ЛогЗагрузки");

	Итоги.ОбработаноЧеков = Результат.ОбработаноЧеков;
	Итоги.ЕстьОшибки      = Результат.ЕстьОшибки;

	УдалитьИзВременногоХранилища(Адрес);

	Возврат Итоги;

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

&НаКлиенте
Процедура ОтменитьЗагрузку(Команда)

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

	ОтменитьЗаданиеНаСервере(ИдентификаторЗадания);
	ИдентификаторЗадания = Неопределено;
	ПоказатьИндикатор(Ложь);

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

&НаСервереБезКонтекста
Процедура ОтменитьЗаданиеНаСервере(ИдентификаторЗадания)

	ДлительныеОперации.ОтменитьВыполнениеЗадания(ИдентификаторЗадания);

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

&НаКлиенте
Процедура ПоказатьИндикатор(Показывать, Процент = 0, Текст = "")

	Элементы.ГруппаПрогресс.Видимость = Показывать;
	ПроцентВыполнения = Процент;
	ТекстВыполнения   = Текст;

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