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


&НаСервере
Функция ФорматироватьДатуДляЭТРАН(ДатаЗнач)
    // Формируем дату в формате dd.MM.yyyy HH:mm:ss
    // Используем Формат с точным указанием разделителей
    Возврат Формат(ДатаЗнач, "ДФ='dd.MM.yyyy HH:mm:ss'");
КонецФункции 

&НаСервере
Функция ДекодироватьHTML(Текст)
    Если ПустаяСтрока(Текст) Тогда
        Возврат Текст;
    КонецЕсли;
    
    Текст = СтрЗаменить(Текст, """, Символ(34));
    Текст = СтрЗаменить(Текст, "'", Символ(39));
    Текст = СтрЗаменить(Текст, "&", "&");
    Текст = СтрЗаменить(Текст, "&lt;", "<");
    Текст = СтрЗаменить(Текст, "&gt;", ">");
    Текст = СокрЛП(Текст);
    
    Возврат Текст;
КонецФункции

&НаСервере
Функция ПолучитьСписокНакладныхВПути(ДатаС, ДатаПо) Экспорт
    
    Результат = Новый Массив;
    
    АдресСервера = "10.10.100.4";
    Порт = 5055;
    
    // Форматируем даты БЕЗ кодирования
    СтрокаДатаС = ФорматироватьДатуДляЭТРАН(ДатаС);
    СтрокаДатаПо = ФорматироватьДатуДляЭТРАН(ДатаПо);
    
    // Формируем URL напрямую, без замены пробелов и двоеточий
    //URL = "http://" + АдресСервера + ":" + Формат(Порт, "ЧГ=0") + "/getBlockInvoiceStatus";
	URL = "/getBlockInvoiceStatus";
    URL = URL + "?fromDate=" + СтрокаДатаС;
    URL = URL + "&toDate=" + СтрокаДатаПо;
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "URL запроса: " + URL);
    
    // HTTP-запрос
    Попытка
		HTTPСоединение = Новый HTTPСоединение(АдресСервера, Порт, , , , 60);
		HTTPЗапрос = Новый HTTPЗапрос(URL);
		HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
		
        Если HTTPОтвет.КодСостояния = 200 Тогда
            XMLСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
            
            ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
                "Получен ответ, длина: " + СтрДлина(XMLСтрока));
            
            Результат = РаспарситьСписокНакладныхВПути(XMLСтрока);
        Иначе
            ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка HTTP: " + HTTPОтвет.КодСостояния);
        КонецЕсли;
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка соединения: " + ОписаниеОшибки());
    КонецПопытки;
    
    Возврат Результат;
    
КонецФункции 

&НаСервере
Функция РаспарситьСписокНакладныхВПути(XMLСтрока) Экспорт
    
    Результат = Новый Массив;
    Чтение = Новый ЧтениеXML;
    
    Попытка
        Чтение.УстановитьСтроку(XMLСтрока);
        
        Пока Чтение.Прочитать() Цикл
            ИмяУзла = ВРег(Чтение.Имя);
            
            // Нашли начало блока накладной
            Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И ИмяУзла = "INVOICE" Тогда
                
                Ид = ""; 
                Ном = "";
                
                // Читаем внутренние теги накладной
                Пока Чтение.Прочитать() Цикл
                    ТекущееИмя = ВРег(Чтение.Имя);
                    
                    // Выход из накладной
                    Если Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента И ТекущееИмя = "INVOICE" Тогда
                        Прервать;
                    КонецЕсли;
                    
                    // Нас интересуют только начала элементов (invoiceID, invNumber)
                    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
                        
                        // Ищем атрибут "value" у текущего тега
                        ЗначениеАтрибута = Чтение.ПолучитьАтрибут("value");
                        
                        Если ТекущееИмя = "INVOICEID" Тогда
                            Ид = ЗначениеАтрибута;
							// Убираем разделители тысяч (запятые)
   							Ид = СтрЗаменить(Ид, ",", "");
                        ИначеЕсли ТекущееИмя = "INVNUMBER" Тогда
                            Ном = ЗначениеАтрибута;
                        КонецЕсли;
                        
                    КонецЕсли;
                КонецЦикла;
                
                Если ЗначениеЗаполнено(Ид) Тогда
                    Результат.Добавить(Новый Структура("InvoiceID, InvNumber", Ид, Ном));
                КонецЕсли;
                
            КонецЕсли;
        КонецЦикла;
        
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка парсинга: " + ОписаниеОшибки());
    КонецПопытки;
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "Парсинг завершен. Найдено: " + Результат.Количество());
    
    Возврат Результат;
    
КонецФункции


&НаСервере
Функция ПолучитьСписокИспорченныхНакладных(ДатаС, ДатаПо) Экспорт
    
    Результат = Новый Массив;
    
    АдресСервера = "10.10.100.4";
    Порт = 5055;
    
    // Формируем даты
    СтрокаДатаС = ФорматироватьДатуДляЭТРАН(ДатаС);
    СтрокаДатаПо = ФорматироватьДатуДляЭТРАН(ДатаПо);
    
    // Формируем URL для испорченных накладных
	URL = "/getBlockInvoiceStatusSpoil";
    URL = URL + "?fromDate=" + СтрокаДатаС;
    URL = URL + "&toDate=" + СтрокаДатаПо;
	//URL = "/getBlockInvoiceStatusSpent?fromDate=" + СтрокаДатаС + "&toDate=" + СтрокаДатаПо;
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "URL (испорченные): " + URL);
    
    Попытка
        HTTPСоединение = Новый HTTPСоединение(АдресСервера, Порт, , , , 60);
        HTTPЗапрос = Новый HTTPЗапрос(URL);
        HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
        
        Если HTTPОтвет.КодСостояния = 200 Тогда
            XMLСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
            
            Если СтрДлина(XMLСтрока) > 150 Тогда
                ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
                    "Получен ответ (испорченные), длина: " + СтрДлина(XMLСтрока) + " байт");
                Результат = РаспарситьСписокИспорченныхНакладных(XMLСтрока);
            КонецЕсли;
        Иначе
            ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка HTTP (испорченные): " + HTTPОтвет.КодСостояния);
        КонецЕсли;
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка соединения (испорченные): " + ОписаниеОшибки());
    КонецПопытки;
    
    Возврат Результат;
    
КонецФункции

&НаСервере
Процедура РаспарситьПолнуюНакладнуюВДокумент(Документ, XMLСтрока) Экспорт
	
	// 1. Очистка табличной части
	Документ.ДанныеНакладной.Очистить();
	
	Чтение = Новый ЧтениеXML;
	Чтение.УстановитьСтроку(XMLСтрока);
	
	ТекущийВагон = Неопределено;
	
	Пока Чтение.Прочитать() Цикл
		ТипУзла = Чтение.ТипУзла;
		ИмяТега = ВРег(Чтение.Имя);
		
		Если ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
			
			// Получаем значение из атрибута value
			Значение = Чтение.ПолучитьАтрибут("value");
			
			// --- ШАПКА ДОКУМЕНТА ---
			Если ИмяТега = "INVNUMBER" Тогда Документ.InvNumber = Значение;
			ИначеЕсли ИмяТега = "INVOICESTATE" Тогда Документ.InvoiceState = Значение;
			ИначеЕсли ИмяТега = "INVOICESTATEID" Тогда Документ.InvoiceStateID = Значение;
			ИначеЕсли ИмяТега = "INVDATECREATE" Тогда Документ.InvDateCreate = ДатаИзЭтрана(Значение);
			ИначеЕсли ИмяТега = "INVDATELOAD" Тогда Документ.InvDateLoad = ДатаИзЭтрана(Значение);
			ИначеЕсли ИмяТега = "INVSENDERNAME" Тогда 
    			Документ.InvSenderName = ?(ЗначениеЗаполнено(Значение), ДекодироватьHTML(Значение), "");
			ИначеЕсли ИмяТега = "INVFROMSTATIONNAME" Тогда Документ.InvFromStationName = ДекодироватьHTML(Значение);
			ИначеЕсли ИмяТега = "FREIGHTNAME" Тогда Документ.FreightName = ДекодироватьHTML(Значение);
			ИначеЕсли ИмяТега = "INVFACTDATETOLOAD" Тогда Документ.InvFactDateToLoad = ДатаИзЭтрана (Значение);
			ИначеЕсли ИмяТега = "INVDATEEXPIRE"	Тогда Документ.InvDateExpire = ДатаИзЭтрана (Значение); 
	        ИначеЕсли ИмяТега = "INVSENDERID" Тогда Документ.InvSenderID = Значение;
			ИначеЕсли ИмяТега = "INVFROMSTATIONCODE" Тогда Документ.InvFromStationCode = Значение;
			ИначеЕсли ИмяТега = "FREIGHTCODE" Тогда Документ.FreightCode = Значение;	
			ИначеЕсли ИмяТега = "FREIGHTEXACTNAME" Тогда Документ.FreightExactName = ДекодироватьHTML(Значение);					
			ИначеЕсли ИмяТега = "INVTYPEID" Тогда Документ.InvTypeID = Значение;
			ИначеЕсли ИмяТега = "INVTYPENAME" Тогда Документ.InvTypeName = ДекодироватьHTML(Значение);
			ИначеЕсли ИмяТега = "INVPARENTID" Тогда Документ.ParentInvoiceID = Значение;
			ИначеЕсли ИмяТега = "INVPARENTNUMBER" Тогда Документ.ParentInvNumber = Значение;
	
				
				
				
				// --- ОБРАБОТКА ВАГОНОВ ---
			ИначеЕсли ИмяТега = "INVCAR" Тогда
				ТекущийВагон = Документ.ДанныеНакладной.Добавить();
				ТекущийВагон.КоличествоЗПУ = 0;
				ТекущийВагон.НомераЗПУ = "";
				
			ИначеЕсли ТекущийВагон <> Неопределено Тогда
				// Мы внутри блока <invCar>, заполняем поля текущей строки
				Если ИмяТега = "CARNUMBER" Тогда ТекущийВагон.CarNumber = Значение;
				ИначеЕсли ИмяТега = "CARTYPECODE" Тогда ТекущийВагон.CarTypeCode = Значение;
				ИначеЕсли ИмяТега = "CARTYPENAME" Тогда ТекущийВагон.CarTypeName = ДекодироватьHTML(Значение);
				ИначеЕсли ИмяТега = "CAROWNERNAME" Тогда ТекущийВагон.CarOwnerName = ДекодироватьHTML(Значение);
				
				// Весовые показатели
				ИначеЕсли ИмяТега = "CARWEIGHTDEP" Тогда 
				    ТекущийВагон.CarTareWeight = ЧислоИзСтрокиЭТРАН(Значение);
				ИначеЕсли ИмяТега = "CARWEIGHTDEPREAL" Тогда 
				    ТекущийВагон.CarWeightDepReal = ЧислоИзСтрокиЭТРАН(Значение);
				ИначеЕсли ИмяТега = "CARTONNAGE" Тогда 
				    ТекущийВагон.CarTonnage = ЧислоИзСтрокиЭТРАН(Значение);
				ИначеЕсли ИмяТега = "CARWEIGHTNET" Тогда 
				    ТекущийВагон.CarWeightNet = ЧислоИзСтрокиЭТРАН(Значение);
				ИначеЕсли ИмяТега = "CARWEIGHTGROSS" Тогда 
				    ТекущийВагон.CarWeightGross = ЧислоИзСтрокиЭТРАН(Значение);
				
				// ЗПУ (внутри вагона)
				ИначеЕсли ИмяТега = "SEALMARKS" И ЗначениеЗаполнено(Значение) Тогда
					ТекущийВагон.КоличествоЗПУ = ТекущийВагон.КоличествоЗПУ + 1;
					ТекущийВагон.НомераЗПУ = ТекущийВагон.НомераЗПУ + ?(ТекущийВагон.НомераЗПУ = "", "", ", ") + Значение;
				КонецЕсли;
			КонецЕсли;
			
		ИначеЕсли ТипУзла = ТипУзлаXML.КонецЭлемента И ИмяТега = "INVCAR" Тогда
			// Вышли из блока вагона
			ТекущийВагон = Неопределено;
		КонецЕсли;
	КонецЦикла;
	
	// Признак досылочной
	Документ.ЯвляетсяДосылочной = ЗначениеЗаполнено(Документ.ParentInvNumber);
	Документ.Иерархия = ?(ЗначениеЗаполнено(Документ.ParentInvNumber), "→ " + Документ.ParentInvNumber, "");
	
	//Документ.Записать();
	
	ЗаписьЖурналаРегистрации("ЗагрузкаЭтран.Отладка", , , , 
		"Парсинг завершен. Вагонов загружено: " + Документ.ДанныеНакладной.Количество());
КонецПроцедуры

&НаСервере
Функция ЧислоИзСтрокиЭТРАН(СтрокаЗнач)
    Если ПустаяСтрока(СтрокаЗнач) Тогда
        Возврат 0;
    КонецЕсли;
    Возврат Число(СтрЗаменить(СтрокаЗнач, ",", "."));
КонецФункции

&НаСервере
Функция ДатаИзЭтрана(СтрДата)
    // Преобразует строку "06.05.2026 23:49:17" в дату 1С
    Попытка
        // Разбиваем строку по всем возможным разделителям
        Части = СтрРазделить(СтрДата, " .:");
        Если Части.Количество() >= 3 Тогда
            // Формируем дату: Год, Месяц, День, Час, Мин, Сек
            Г = Число(Части[2]); 
            М = Число(Части[1]); 
            Д = Число(Части[0]);
            час = ?(Части.Количество() > 3, Число(Части[3]), 0);
            мин = ?(Части.Количество() > 4, Число(Части[4]), 0);
            сек = ?(Части.Количество() > 5, Число(Части[5]), 0);
            
            Возврат Дата(Г, М, Д, час, мин, сек);
        КонецЕсли;
    Исключение
        Возврат '00010101'; // Если дата пустая или битая
    КонецПопытки;
    Возврат '00010101';
КонецФункции


&НаСервере
Процедура СоздатьДокументЭтранНакладная(InvoiceID, InvNumber) Экспорт
    
    // Проверяем, существует ли документ
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |    Документ.Ссылка
    |ИЗ
    |    Документ.ЭтранНакладная КАК Документ
    |ГДЕ
    |    Документ.InvoiceID = &InvoiceID
    |    И Документ.ПометкаУдаления = ЛОЖЬ";
    
    Запрос.УстановитьПараметр("InvoiceID", InvoiceID);
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    Если Выборка.Следующий() Тогда
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Документ уже существует: " + InvNumber);
        Возврат;
    КонецЕсли;
    
    // Создаем новый документ
    НовыйДокумент = Документы.ЭтранНакладная.СоздатьДокумент();
    НовыйДокумент.InvoiceID = InvoiceID;
    НовыйДокумент.InvNumber = InvNumber;
    НовыйДокумент.Дата = ТекущаяДата();
        
    // Загружаем XML для ОДНОЙ конкретной накладной
    IPАдрес = "10.10.100.4";
    ПортЧислом = 5055;
    ПутьРесурса = "/getBlockInvoiceID?invoiceID=" + InvoiceID;
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран.Отладка", УровеньЖурналаРегистрации.Информация, , , 
        "Запрашиваем полную накладную по InvoiceID: " + InvoiceID);
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран.Отладка", УровеньЖурналаРегистрации.Информация, , , 
        "URL для полной накладной: " + ПутьРесурса);
    
    Попытка
        HTTPСоединение = Новый HTTPСоединение(IPАдрес, ПортЧислом, , , , 60);
        HTTPЗапрос = Новый HTTPЗапрос(ПутьРесурса);
        HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
        
        Если HTTPОтвет.КодСостояния = 200 Тогда
            
            Попытка
                XMLСтрока = HTTPОтвет.ПолучитьТелоКакСтроку();
                
                Если Не ПустаяСтрока(XMLСтрока) И СтрДлина(XMLСтрока) > 150 Тогда
                     
                 // Парсим XML напрямую
                 РаспарситьПолнуюНакладнуюВДокумент(НовыйДокумент, XMLСтрока);
                    
                КонецЕсли;
                
            Исключение
                ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
                    "Ошибка получения тела ответа для " + InvNumber + ": " + ОписаниеОшибки());
            КонецПопытки;
            
        Иначе
            ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка HTTP для " + InvNumber + ": " + HTTPОтвет.КодСостояния);
        КонецЕсли;
        
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка загрузки данных для " + InvNumber + ": " + ОписаниеОшибки());
    КонецПопытки;
    
    // Запись документа
    Попытка
        НовыйДокумент.Записать();
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Создан документ: " + InvNumber + ", вагонов: " + НовыйДокумент.ДанныеНакладной.Количество());
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка записи документа " + InvNumber + ": " + ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

&НаСервере
Процедура ПометитьДокументНаУдаление(InvoiceID)
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |    Документ.Ссылка
    |ИЗ
    |    Документ.ЭтранНакладная КАК Документ
    |ГДЕ
    |    Документ.InvoiceID = &InvoiceID
    |    И Документ.ПометкаУдаления = ЛОЖЬ";
    
    Запрос.УстановитьПараметр("InvoiceID", InvoiceID);
    Результат = Запрос.Выполнить();
    Выборка = Результат.Выбрать();
    
    Если Выборка.Следующий() Тогда
        ДокументОбъект = Выборка.Ссылка.ПолучитьОбъект();
        ДокументОбъект.УстановитьПометкуУдаления(Истина);
        ДокументОбъект.Записать();
        
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Документ помечен на удаление: InvoiceID " + InvoiceID);
    КонецЕсли;
КонецПроцедуры


&НаСервере
Функция СоздатьНовыйДокументСпискаНакладныхВПути(ДатаНачала, ДатаОкончания) Экспорт
    
    // 1. Проверяем, не создавали ли мы уже документ за этот точный интервал
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ Документ.ЭтранСписокНакладныхВПути 
        |ГДЕ ДатаС = &ДатаНачала И ДатаПо = &ДатаОкончания И Проведен";
    Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);
    Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);
    
    Если Не Запрос.Выполнить().Пустой() Тогда
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Документ за период " + ДатаНачала + " - " + ДатаОкончания + " уже существует. Пропуск.");
        Возврат Неопределено;
    КонецЕсли;

    // 2. Получаем данные
    Накладные = ПолучитьСписокНакладныхВПути(ДатаНачала, ДатаОкончания);
    
    Если Накладные = Неопределено Или Накладные.Количество() = 0 Тогда
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Нет данных для создания документа за час " + Формат(ДатаНачала, "ДФ=HH:00"));
        Возврат Неопределено;
    КонецЕсли;
    
    // 3. Создаем документ
    НовыйДокумент = Документы.ЭтранСписокНакладныхВПути.СоздатьДокумент();
    НовыйДокумент.Дата = ТекущаяДата();
    НовыйДокумент.ДатаС = ДатаНачала;
    НовыйДокумент.ДатаПо = ДатаОкончания;
    
    Для Каждого Накладная Из Накладные Цикл
        // Проверка на дубли внутри массива (на случай, если ЭТРАН вернул одну накладную дважды)
        ПараметрыПоиска = Новый Структура("InvoiceID", Накладная.InvoiceID);
        Если НовыйДокумент.СтатусыНакладных.НайтиСтроки(ПараметрыПоиска).Количество() > 0 Тогда
            Продолжить;
        КонецЕсли;
        
        НоваяСтрока = НовыйДокумент.СтатусыНакладных.Добавить();
        НоваяСтрока.InvoiceID = Накладная.InvoiceID;
        НоваяСтрока.НомерНакладной = Накладная.InvNumber;
        НоваяСтрока.Статус = "В пути";
    КонецЦикла;
    
    Попытка
        НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
        
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Создан документ 'В пути' за час " + Формат(ДатаНачала, "ДФ=HH:00") + 
            ", накладных: " + НовыйДокумент.СтатусыНакладных.Количество());
            
        // 4. Создаем детальные накладные только после успешной записи основного документа
        Для Каждого СтрокаТЧ Из НовыйДокумент.СтатусыНакладных Цикл
		    Попытка
		        // ✅ Очищаем ID от запятых и пробелов перед передачей
		        ЧистыйID = СтрЗаменить(СтрЗаменить(СтрокаТЧ.InvoiceID, ",", ""), " ", "");
		        СоздатьДокументЭтранНакладная(ЧистыйID, СтрокаТЧ.НомерНакладной);
		    Исключение
		        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
		            "Ошибка создания детальной накладной ID " + СтрокаТЧ.InvoiceID + ": " + ОписаниеОшибки());
		    КонецПопытки;
        КонецЦикла;
        
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Не удалось записать документ ЭтранСписокНакладныхВПути: " + ОписаниеОшибки());
        Возврат Неопределено;
    КонецПопытки;
    
    Возврат НовыйДокумент.Ссылка;
    
КонецФункции

&НаСервере
Функция СоздатьНовыйДокументСпискаИспорченныхНакладных(ДатаНачала, ДатаОкончания)
    Накладные = ПолучитьСписокИспорченныхНакладных(ДатаНачала, ДатаОкончания);
    
    Если Накладные.Количество() = 0 Тогда
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Нет испорченных накладных за час " + Формат(ДатаНачала, "ДФ=HH:00"));
        Возврат Неопределено;
    КонецЕсли;
    
    НовыйДокумент = Документы.ЭтранСписокИспорченыхНакладных.СоздатьДокумент();
    НовыйДокумент.ДатаС = ДатаНачала;
    НовыйДокумент.ДатаПо = ДатаОкончания;
    НовыйДокумент.Дата = ТекущаяДата();
    
    Для Каждого Накладная Из Накладные Цикл
        НоваяСтрока = НовыйДокумент.СтатусыНакладных.Добавить();
        НоваяСтрока.InvoiceID = Накладная.InvoiceID;
        НоваяСтрока.НомерНакладной = Накладная.InvNumber;
        НоваяСтрока.Статус = "Испорчен";
    КонецЦикла;
    
    НовыйДокумент.Записать();
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "Создан документ 'Испорченные' за час " + Формат(ДатаНачала, "ДФ=HH:00") + 
        ", накладных: " + Строка(Накладные.Количество()));
    
    Для Каждого Накладная Из Накладные Цикл
        Попытка
            ПометитьДокументНаУдаление(Накладная.InvoiceID);
        Исключение
            ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
                "Ошибка пометки на удаление " + Накладная.InvNumber);
        КонецПопытки;
    КонецЦикла;
    
    Возврат НовыйДокумент;
КонецФункции

&НаСервере
Процедура АвтозагрузкаНакладныхЭТРАН() Экспорт
    
    // Загружаем предыдущие полные сутки
    Вчера = ТекущаяДата() - 86400; // 24 часа назад
    НачалоСуток = Дата(Год(Вчера), Месяц(Вчера), День(Вчера), 0, 0, 0);
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "=== Начало загрузки за сутки: " + Формат(НачалоСуток, "ДФ=dd.MM.yyyy") + " ===");
    
    ВсегоВПути = 0;
    ВсегоИспорчено = 0;
    ВсегоЧасовСДанными = 0;
    
    Для Час = 0 По 23 Цикл
        // Вычисляем начало и конец часа
        НачалоЧаса = НачалоСуток + (Час * 3600);
        КонецЧаса = НачалоЧаса + 3599; // 59 минут 59 секунд
        
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
            "Обработка часа: " + Формат(НачалоЧаса, "ДФ=HH:mm") + " - " + Формат(КонецЧаса, "ДФ=HH:mm"));
        
        // 1. Накладные в пути за этот час
        НакладныеВПути = ПолучитьСписокНакладныхВПути(НачалоЧаса, КонецЧаса);
        
        Если НакладныеВПути.Количество() > 0 Тогда
            СоздатьНовыйДокументСпискаНакладныхВПути(НачалоЧаса, КонецЧаса);
            ВсегоВПути = ВсегоВПути + НакладныеВПути.Количество();
            ВсегоЧасовСДанными = ВсегоЧасовСДанными + 1;
        КонецЕсли;
        
        // 2. Испорченные накладные за этот час
        ИспорченныеНакладные = ПолучитьСписокИспорченныхНакладных(НачалоЧаса, КонецЧаса);
        
        Если ИспорченныеНакладные.Количество() > 0 Тогда
            СоздатьНовыйДокументСпискаИспорченныхНакладных(НачалоЧаса, КонецЧаса);
            ВсегоИспорчено = ВсегоИспорчено + ИспорченныеНакладные.Количество();
            Если НакладныеВПути.Количество() = 0 Тогда
                ВсегоЧасовСДанными = ВсегоЧасовСДанными + 1;
            КонецЕсли;
        КонецЕсли;
        
    КонецЦикла;
    
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "=== Загрузка завершена ===");
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "Всего часов с данными: " + ВсегоЧасовСДанными + " из 24");
    ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Информация, , , 
        "Накладных в пути: " + ВсегоВПути + ", испорчено: " + ВсегоИспорчено);
    
КонецПроцедуры

&НаСервере
Функция РаспарситьСписокИспорченныхНакладных(XMLСтрока) Экспорт
    
    Результат = Новый Массив;
    Чтение = Новый ЧтениеXML;
    
    Попытка
        Чтение.УстановитьСтроку(XMLСтрока);
        
        Пока Чтение.Прочитать() Цикл
            // Приводим имя тега к верхнему регистру для надежности
            ИмяУзла = ВРег(Чтение.Имя);
            
            // Нашли начало блока накладной <invoice>
            Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента И ИмяУзла = "INVOICE" Тогда
                
                Ид = ""; 
                Ном = "";
                
                // Читаем всё внутри текущего блока <invoice>
                Пока Чтение.Прочитать() Цикл
                    ТекущееИмя = ВРег(Чтение.Имя);
                    
                    // Если дошли до конца </invoice>, выходим из внутреннего цикла
                    Если Чтение.ТипУзла = ТипУзлаXML.КонецЭлемента И ТекущееИмя = "INVOICE" Тогда
                        Прервать;
                    КонецЕсли;
                    
                    // Ищем нужные поля в начале элементов
                    Если Чтение.ТипУзла = ТипУзлаXML.НачалоЭлемента Тогда
                        
                        // Получаем данные из атрибута value="..."
                        ЗначениеАтрибута = Чтение.ПолучитьАтрибут("value");
                        
                        Если ТекущееИмя = "INVOICEID" Тогда
                            Ид = ЗначениеАтрибута;  
							// Убираем разделители тысяч (запятые)
    						Ид = СтрЗаменить(Ид, ",", "");
                        ИначеЕсли ТекущееИмя = "INVNUMBER" Тогда
                            Ном = ЗначениеАтрибута;
                        КонецЕсли;
                        
                    КонецЕсли;
                КонецЦикла;
                
                // Если данные найдены, добавляем структуру в массив
                Если ЗначениеЗаполнено(Ид) Тогда
                    Результат.Добавить(Новый Структура("InvoiceID, InvNumber", Ид, Ном));
                КонецЕсли;
                
            КонецЕсли;
        КонецЦикла;
        
    Исключение
        ЗаписьЖурналаРегистрации("ЗагрузкаЭтран", УровеньЖурналаРегистрации.Ошибка, , , 
            "Ошибка парсинга испорченных накладных: " + ОписаниеОшибки());
    КонецПопытки;
    
    Возврат Результат;
    
КонецФункции