Top.Mail.Ru

Автомат создания и корректировки цепочки документов в 1С для Ордерного склада

При работе с ордерным складом может сложиться ситуация, когда нужно скорректировать данные в цепочке документов сразу «с двух концов». Например, это актуально для интернет-магазинов, у которых в любой момент по звонку клиента может быть изменён документ Заказ клиента, а расходный ордер и отборы уже в работе. Необходим инструмент логического согласования всех нижеследующих документов для того, чтобы на этапе отгрузки и оплаты не возникало путаницы с документами. Ниже я расскажу о механизме, который обеспечит корректное движение информации в цепочке при  внесении изменений.

В каких ситуациях не подходит ручное создание документов

Довольно часто возникает задача автоматизации процессов документооборота в организации, имеющей ордерные склады. Нередко имеем следующую рабочую схему документов, возникающих от Заказа клиента до момента отгрузки товара клиенту:

Схема движения документов для Ордерного склада

Конечно, эту цепочку документов можно создать вручную — кнопка «Создать на основании» при правильной настройке системы будет доступна для пользователя. Но эта кнопка не поможет в случаях, когда возрастает количество документов, приходящих от клиента, или усложняется структура складов, или документы «Заказ клиента» уже попадают в систему в результате автоматизации типа импорта данных из какого-либо интернет-магазина.

Первым этапом может быть простое создание документов следующего уровня автоматически. Например, при проведении документа проверяем все условия, выполнение которых допускает создание нижеследующего документа, и создаем его. Условия могут быть самые разные, как и статусы документов, и различного рода согласования внутри организации. Могут быть звонки клиенту или наличие предоплаты по документу. На этом этапе все может быть завязано на реквизиты документа и реализовано в Модуле объекта документа.

Как должно работать автоматическое создание цепочки документов

Рабочей становится схема, когда мы на основании документа Заказ клиента «автоматически» создаем Расходный ордер на товары. Если бизнес-процесс организации требует выполнения каких-либо условий, то также при проведении проверяем их выполнение и в нужный момент создаем документ Отбор (размещение) товаров.

В дальнейшем, чтобы не путаться, документ, собирающий товар для клиента, будем называть просто «Отбор товаров», а документ, выполняющий функцию отмены собранного товара и размещения его назад на складе, будем называть «Размещением товара».

В зависимости от состояния склада на основании Расходного ордера может быть создан один или большее количество отборов, которые и попадают в работу к кладовщикам-сборщикам товаров на складе. В зависимости от загрузки склада время взятия в работу и выполнения этих документов может быть различным, но наверняка не прогнозируемым абсолютно точно.

Документ Отбор товара может быть выполнен с двумя статусами: либо весь товар собран и документ выполнен без ошибок, либо в силу тех или иных причин кладовщик не смог собрать весь требуемый товар, и тогда Отбор товара будет закрыт в статусе «Выполнено с ошибками». Задача минимизации количества таких отборов выходит за рамки вопросов, рассматриваемых в нашей статье. Мы просто продолжим рассуждать дальше. Склад не смог собрать какую-либо номенклатуру. Информация об этом есть в документе Отбор товаров. Нужно каким-то образом передать эти данные в документы более высокого уровня. Простое решение может быть в той же схеме, что и движение данных в прямом направлении. При проведении соответствующего документа в модуле объекта размещаем процедуру, которая внесет изменения в документ более высокого уровня.

Схема обратного движения документов для Ордерного склада

Что может пойти не так в автоматическом создании цепочки документов

В приведенной выше схеме все будет работать корректно до тех пор, пока к разработчику не придут менеджеры с рассказом про очень важного клиента, который после оформления заказа вдруг решил его изменить. Причем изменения нужно оформить не отдельным документом, типа возврат товара или наоборот еще один заказ, а именно изменением текущего Заказа клиента. И уже в этой схеме появления изменений мы получаем поводы для изменения нашей цепочки документов в одно и то же время, но одновременно и с разных сторон.

Варианты изменений могут быть как простые, так и не очень. Например, клиент попросил уменьшить количество (или вообще вычеркнуть) в позиции, по которой склад не смог собрать запланированное изначально количество. Какие изменения первыми попадут в Расходный ордер? Не перезатрут ли они там какие-то данные? Или может быть, просуммируются и наложатся друг на друга? Такие вопросы будут возникать на каждом этапе, где на встречу друг другу будет идти информация про изменения.

Как правильно реализовать автомат создания цепочки документов

Итак, приступим к реализации нашей задачи. Некоторые изменения придется внести в основную конфигурацию. Часть доработок будет помещена в расширение конфигурации. Ну а основные функции по обработке возникающих задач мы разместим как во внешней обработке.

В основную конфигурацию для хранения возникающих задач добавим регистр сведений:

Регистр сведений ОбъектыДляКорректировки

ОбъектыДляКорректировки

С измерениями:

Идентификатор – УникальныйИдентификатор()
Объект – ДокументСсылка.ЗаказКлиента, ДокументСсылка.РасходныйОрдерНаТовары, ДокументСсылка.ОтборРазмещениеТоваров, ДокументСсылка.ПриходныйОрдерНаТовары
Задача – ПеречислениеСсылка.АСК_ЗадачиАК

ДатыПостановкиВОчередь и ДатаОбработки это дата\время появления задачи и её выполнения.
Ошибка – булевское значение, показывающее, что обработка задачи завершилась некорректно.

Поля: Ошибка, ТипОшибки, ОписаниеОшибки и ДополнительныеДанные — это текстовые поля, в которые можно заносить информацию для анализа работы нашей обработки.

Также добавим перечисление АСК_ЗадачиАК со следующими значениями:

СоздатьИзменитьРОпоЗКСоздать изменить Расходный ордер по Заказу клиента
СоздатьИзменитьОТпоРОСоздать изменить Отбор товаров по Расходному ордеру
СоздатьИзменитьРТпоРОСоздать изменить Размещение товаров по Расходному ордеру
ИзменитьРОпоОТИзменить Расходный ордер по Отбору товаров
ИзменитьЗКпоРОИзменить Заказ клиента по Расходному ордеру

Значение перечисления АСК_ЗадачиАК
Перечисление АСК_ЗадачиАК
Перечисление АСК_ЗадачиАК

Создадим расширение конфигурации АСК и добавим в него нужные нам документы, которые будем дорабатывать.

Расширение конфигурации АСК

Далее во внешней обработке добавляем служебные функции СведенияОВнешнейОбработке() и ВыполнитьКоманду()

Функция СведенияОВнешнейОбработке() Экспорт
	
	ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке();
	ПараметрыРегистрации.Информация = НСтр("ru = 'Автомат создания и корректировки документов");
	ПараметрыРегистрации.Вид = "ДополнительнаяОбработка";
	ПараметрыРегистрации.Версия = "4.12.23";
	ПараметрыРегистрации.ВерсияБСП = "2.3.3.0"; // 3.1.5.458
	ПараметрыРегистрации.БезопасныйРежим = Ложь;
	ПараметрыРегистрации.Наименование = "Автомат создания и корректировки документов";
	
    ТаблицаКоманд = ТаблицаКоманд();
	
	ДобавитьКоманду(ТаблицаКоманд,
		Нстр("ru = '01. Создать\изменить Расходный ордер по Заказу клиента'"),
		"ОбработатьЗадачуСоздатьИзменитьРОпоЗК",
		"ВызовСерверногоМетода",
		Ложь,
		"");
	
	ДобавитьКоманду(ТаблицаКоманд,
		Нстр("ru = '02. Создать\Изменить Отборы по Расходному ордеру'"),
		"ОбработатьЗадачуСоздатьИзменитьОТпоРО",
		"ВызовСерверногоМетода",
		Ложь,
		"");
	
	ДобавитьКоманду(ТаблицаКоманд,
		Нстр("ru = '03. Создать\Изменить Размещения по Расходному ордеру''"),
		"ОбработатьЗадачуСоздатьИзменитьРТпоРО",
		"ВызовСерверногоМетода",
		Ложь,
		"");
	
	ДобавитьКоманду(ТаблицаКоманд,
		Нстр("ru = '04. Изменить Расходный ордер по Отбору'"),
		"ОбработатьЗадачуИзменитьРОпоОТ",
		"ВызовСерверногоМетода",
		Ложь,
		"");	
	
	ДобавитьКоманду(ТаблицаКоманд,
		Нстр("ru = '05. Изменить Заказ клиента по Расходному ордеру'"),
		"ОбработатьЗадачуИзменитьЗКпоРО",
		"ВызовСерверногоМетода",
		Ложь,
		"");
		
    ПараметрыРегистрации.Вставить("Команды", ТаблицаКоманд);
	
    Возврат ПараметрыРегистрации;
	
КонецФункции
Процедура ВыполнитьКоманду(Идентификатор, ДополнительныеПараметры = Неопределено) Экспорт
	
	// 01. Создать изменить Расходный ордер по Заказу клиента
	Если Идентификатор = "ОбработатьЗадачуСоздатьИзменитьРОпоЗК" Тогда
		ОбработатьЗадачуСоздатьИзменитьРОпоЗК();
		
	// 02. Создать изменить Отбор товаров по Расходному ордеру
	ИначеЕсли Идентификатор = "ОбработатьЗадачуСоздатьИзменитьОТпоРО" Тогда 
		ОбработатьЗадачуСоздатьИзменитьОТпоРО();
		
	// 03. Создать\Изменить Размещения по Расходному ордеру
	ИначеЕсли Идентификатор = "ОбработатьЗадачуСоздатьИзменитьРТпоРО" Тогда
		ОбработатьЗадачуСоздатьИзменитьРТпоРО();
		
	// 04. Изменить Расходный ордер по Отбору
	ИначеЕсли Идентификатор = "ОбработатьЗадачуИзменитьРОпоОТ" Тогда
	    ОбработатьЗадачуИзменитьРОпоОТ();
		
	// 05. Изменить Заказ клиента по Расходному ордеру
	ИначеЕсли Идентификатор = "ОбработатьЗадачуИзменитьЗКпоРО" Тогда
		ОбработатьЗадачуИзменитьЗКпоРО();
		
	КонецЕсли;
	
КонецПроцедуры

Давайте рассмотрим работу нашей обработки АвтоматСозданияКорректировки на примере самой первой задачи «01. Создать изменить Расходный ордер по Заказу клиента».

Сперва в модуль объекта документа Заказ клиента в нашем расширении внесём следующую функцию:

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

Потом, в зависимости от бизнес-логики организации, в процедуру проверки выполнения условий по заказу, которые должны выполниться, для того чтобы задача создалась, добавляем проверку на их выполнение. Для начала просто проверим на статус документа Заказ клиента, чтобы он был не начальный «подготовлен», а уже обработанный менеджером «КОбеспечению»:

// возвращает Истина если по заказу все дополнительные условия соблюдены
&НаСервере
Функция ПроверитьВыполнениеУсловийПоЗаказу(Заказ)
	Если Заказ.Статус = Перечисления.СтатусыЗаказовКлиентов.КОбеспечению Тогда		
		Возврат Истина		
	Иначе		
		Возврат Ложь		
	КонецЕсли;		
КонецФункции

Для добавления задачи в регистр ОбъектыДляКорректировки в общем модуле расширения создадим процедуру:

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

При начале работы любой из команд нам нужно получить данные из регистра по имеющимся для этой команды задачам:

Задача = Перечисления.АСК_ЗадачиАК.СоздатьИзменитьРОпоЗК; РезультатЗапроса = ПолучитьОбъектыДляКорректировки(Задача);

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

Ну а далее в любой команде нашей обработки будет такая процедура:

// 01. Создать изменить Расходный ордер по Заказу клиента
Процедура ОбработатьЗадачуСоздатьИзменитьРОпоЗК() Экспорт
	
	УстановитьПривилегированныйРежим(Истина);
	
		Задача = Перечисления.АСК_ЗадачиАК.СоздатьИзменитьРОпоЗК;
		РезультатЗапроса = ПолучитьОбъектыДляКорректировки(Задача);
	
	Если РезультатЗапроса.Пустой() Тогда 
		Возврат;
	КонецЕсли;
	
	Выборка = РезультатЗапроса.Выбрать();
	
	Пока Выборка.Следующий() Цикл
		
		проверим не завершенную встречную задачу по изменению Заказа по РСО
		Если ПроверитьНаличиеЗадачи(Выборка.Объект) Тогда
			Продолжить;
		КонецЕсли;
		
		НачатьТранзакцию();
		
		БлокировкаЗаписи = Новый БлокировкаДанных;
		ЭлементБлокировки = БлокировкаЗаписи.Добавить("РегистрСведений.ОбъектыДляКорректировки");
		ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
		ЭлементБлокировки.УстановитьЗначение("Идентификатор", Выборка.Идентификатор);
		БлокировкаЗаписи.Заблокировать();
		
		ОшибкиОбработки = ПолучитьСтруктуруОшибки();
		ОшибкиОбработки.Вставить("ЕстьВРаботе", Ложь);
		
		// если есть другие варинты отгрузки  тогда завершим задачу с ошибкой
		Для Каждого Товар Из Выборка.Объект.Товары Цикл
			Если Товар.Номенклатура.ТипНоменклатуры = Перечисления.ТипыНоменклатуры.Товар
				И НЕ Товар.Отменено
				И НЕ (Товар.ВариантОбеспечения = Перечисления.ВариантыОбеспечения.Отгрузить
					ИЛИ Товар.ВариантОбеспечения = Перечисления.ВариантыОбеспечения.ОтгрузитьОбособленно) Тогда
				ОшибкиОбработки.Ошибка = Истина;
				ОшибкиОбработки.ТипОшибки = "Не корректные варианты обеспечения";
				ОшибкиОбработки.ОписаниеОшибки = "В заказе есть строки с состоянием обеспечения отличным от ""Отгрузить"" и ""Отгрузить обособленно""";
				Прервать;
			КонецЕсли;	
		КонецЦикла;
		
		// если весь товар отгружается с неордерного склада то тогда
		Если ПроверитьТоварНаОрдерныйСклад(Выборка.Объект) Тогда
		    ОшибкиОбработки.Ошибка = Истина;
			ОшибкиОбработки.ТипОшибки = "Отгузка с не ордерного склада";
			ОшибкиОбработки.ОписаниеОшибки = "В заказе все товары отгружаются с не ордерного склада";
		КонецЕсли;
		
		Попытка
			Если НЕ ОшибкиОбработки.Ошибка Тогда
				СкорректироватьРСОПоЗаказуКлиента(Выборка.Объект, ОшибкиОбработки);
				ЗафиксироватьТранзакцию();
			ИначеЕсли ТранзакцияАктивна() Тогда
				ОтменитьТранзакцию();
			КонецЕсли;			
		Исключение				
			Если ТранзакцияАктивна() Тогда
				ОтменитьТранзакцию();
			КонецЕсли;			
			ОшибкиОбработки.Ошибка = Истина;
			ОшибкиОбработки.ТипОшибки = "Неизвестная ошибка";
			Если Не ЗначениеЗаполнено(ОшибкиОбработки.ОписаниеОшибки) Тогда
				ОшибкиОбработки.ОписаниеОшибки = ОписаниеОшибки();
			КонецЕсли;			
		КонецПопытки;
		
		// Отразим результат обработки в регистре сведений
		МенеджерЗаписи = РегистрыСведений.ОбъектыДляКорректировки.СоздатьМенеджерЗаписи();
		
		МенеджерЗаписи.Объект				  = Выборка.Объект;
		МенеджерЗаписи.Идентификатор		  = Выборка.Идентификатор;
		МенеджерЗаписи.Задача				  = Выборка.Задача;
		МенеджерЗаписи.ДатаПостановкиВОчередь = Выборка.ДатаПостановкиВОчередь;
			
		МенеджерЗаписи.Прочитать();
			
		Если МенеджерЗаписи.Выбран() Тогда
			МенеджерЗаписи.Обработано	  = Истина;
			МенеджерЗаписи.ДатаОбработки  = ТекущаяДата();
			МенеджерЗаписи.Ошибка		  = ОшибкиОбработки.Ошибка;
			МенеджерЗаписи.ТипОшибки	  = ОшибкиОбработки.ТипОшибки;	
			МенеджерЗаписи.ОписаниеОшибки = ОшибкиОбработки.ОписаниеОшибки;
		КонецЕсли;
		
		МенеджерЗаписи.Записать();
		
		Если ОшибкиОбработки.ЕстьВРаботе и Не ОшибкиОбработки.Ошибка Тогда 
			АСК_АвтоматСоздания.ДобавитьВЗадачиАК(Выборка.Объект, Перечисления.АСК_ЗадачиАК.СоздатьИзменитьРОпоЗК);
		КонецЕсли;
					
	КонецЦикла;

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

Таким образом, использование предложенного механизма позволяет содержать всю цепочку документов от Заказа клиента до Отбора и Размещения товаров в логической целостности. События, возникающие для обработки мы завязали на проведение изменённых документов, а сама обработка событий живет во внешней обработке, что позволяет оперативно вносить корректировки в механизм обработки документов без модификации конфигурации.

Comments

So empty here ... leave a comment!

Добавить комментарий

Sidebar