Today

Типовое внедрение адресного хранения в 1C:ERP или архитектурная ересь во благо бизнеса

Компания, на склады которой мы пришли для внедрения адресного хранения остатков, несколько лет жила на "простом складе": проведение накладной или реализации не сопровождалось складскими ордерами, и все до определенного момента были счастливы.

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

Подготовка плацдарма

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

Примите во внимание тот факт, что для 1С: ERP (УТ/КА/УХ - не суть важно) ячеистый склад - это не просто набор полок для хранения, а строгий логистический конвейер. Когда вы будете переносить вашу топологию с бумаги в систему, то структура склада потребует от вас ОБЯЗАТЕЛЬНОГО наличия по крайней мере одной зоны приемки и зоны отгрузки (ячеек с типом "Отгрузка" и типом "Приемка").

Логика здесь простая - ТМЦ просто не может перемещаться сразу же в ячейки стеллажей, минуя зону приемки (контроля качества поступивших товаров/материалов), а обязывает проходить через транзитные зоны.

  • Ячейки с типом "Приемка" - это виртуальный или реальный пандус. Сюда товар падает в момент проведения Приходного ордера, и именно отсюда кладовщик будет забирать его для размещения в адресные ячейки.
  • Ячейки с типом "Отгрузка": это ворота на выход со склада. Сюда товар перемещается из зоны хранения при сборке заказа, чтобы дожидаться передачи клиенту или погрузки в транспортное средство для перемещения.

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

Графически данный процесс можно визуализировать с помощью блок-схемы ниже (реальный пример с проекта):

Как только топология склада будет ясна и понятна, требуется произвести вашему складу "пересадку мозга", т.е. объяснить самой 1С, что этот склад больше не работает по-старому, и произвести настройки ордерной схемы документооборота в системе в карточке вашего склада:

Заходим в карточку склада (вкладка "Ордерная схема и структура") и переключаем два судьбоносных параметра:

  • Включаем ордерную схему документооборота (ставим галки "при поступлении" и "при отгрузке").

Это тот самый момент невозврата. С этой секунды мы жестко разрываем финансовый и складской учет. Проведение накладной бухгалтером больше не двигает реальный товар, а лишь создает распоряжение для склада. Теперь без ордера товар со склада не уйдет и на склад не попадет.

  • В поле "Ячейки" выбираем значение "Использовать для хранения остатков номенклатуры".

Это главный триггер адресного склада. Если выбрать вариант "для справочного размещения", то ячейки станут просто текстовыми наклейками на стеллажах, которые ни к чему не обязывают. А нам нужен строгий количественный учет остатков в каждой конкретной ячейке.

И вот только после того, как карточка склада записана с этими настройками, система разрешает нам перейти к генерации топологии.

Генерация топологии склада

Вбивать сотни ячеек вручную - удел мазохистов, поэтому самый простой и надежный способ описать топологию склада в системе 1С:ERP - использовать встроенную обработку "Генерация топологии склада". Работает она быстро и просто, главное - правильно задать матрицу.

В подсистеме "Склад и доставка" в разделе "Сервис" ищем данную обработку и запускаем.

Здесь задается структура адресов (ряды, стеллажи, ярусы...), но ваша радость была бы не полной, если бы 1С не потребовала заполнить два важнейших аналитических реквизита: Типоразмер и Область хранения.

Как это должно работать в идеальном мире:

  • Типоразмер ячейки: система должна знать точные физические габариты каждой полки (высота, ширина, глубина, грузоподъемность). Это нужно, чтобы умный алгоритм сам понимал, что, например, "этот материал на верхний ярус не влезет, а вот сюда встанут ровно две паллеты".
  • Область хранения: Это логическое объединение ячеек с одинаковыми правилами. Например, отдельная зона для скоропортящихся товаров (в холодильники), отдельная зона для тяжелого негабарита (напольное хранение), отдельная для мелочевки и т.д.

Суровая реальность:

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

Данного "изящного" действия было достаточно, чтобы ядро модуля адресного склада "взлетело", и обработка сгенерировала нам полторы сотни ячеек с нужными настройками, позволив отложить сложные логистические расчеты весов и объемов до лучших времен (или навсегда), а кладовщики получили то, что им и было нужно - понятные адреса ячеек без лишнего геморроя.

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

Упаковки преткновения

В обычной жизни бухгалтерии или менеджеров товар чаще всего измеряется, закупается и принимается в базовых единицах измерения: штуках, метрах, литрах, килограммах. Но когда мы включаем адресное хранение остатков, то у системы просыпается логистическое эго. Программа заявляет: "Я не могу положить абстрактную штуку на конкретную полку. Дайте мне физический объект! В чем лежит эта ваша штука? В коробке? На паллете? В пакете?"

Важная архитектурная особенность любого адресного склада:

  • Товар обязан иметь заполненную упаковку в складском документе;
  • Без упаковки 1С просто блокирует транзакцию, не позволяя ни положить товар в ячейку, ни забрать его оттуда.

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

Раз уж мы договорились на предыдущем шаге использовать безразмерные ячейки, то потребуются такие же фиктивные упаковки:

  • Создаются универсальные наборы упаковок (например, один общий набор, где 1 штука равна 1 упаковке без указания точных габаритов и веса).
  • Через обработку "Групповое измерение реквизитов" установить значение реквизита "Использовать упаковки" в старых карточках номенклатуры и привязать к ним этот универсальный набор.

Но тут нас ждала засада в реальном времени. Бухгалтеры и кладовщики, оформляя документы по старинке, постоянно забывали заполнять колонку "Упаковка", либо заполняли ордера не упаковками, а базовыми единицами измерения. Документы не проводились, сыпались красные ошибки, градус нервозности повышался.

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

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

Для этого в документе "Приходный ордер на товары", ровно как и в документе "Расходный ордер на товары" была прикручена автоматизация в виде простой команды на форме, при нажатии на которую алгоритм пробегался по всей табличной части ордера, включал и заполнял внутри каждой номенклатурной позиции упаковки (если не было включено), и заполнял упаковку во всей табличной части документа. В итоге мы получили около идеальное решение: система автоматизирует данные только тогда и только для тех товаров, которые физически пересекают границу нашего адресного склада.

&НаКлиенте
Процедура ВключитьУпаковкиКоманда(Команда)
	
	Если Объект.Товары.Количество() = 0 Тогда
		Возврат; 
	КонецЕсли;
	
	Для Каждого СтрокаТЧ Из Объект.Товары Цикл
		Если СтрокаТЧ.Количество = 0 Тогда
			ПоказатьПредупреждение(Неопределено, "В таблице есть товары с незаполненным количеством!" + Символы.ПС + "Сначала укажите количество, а затем нажимайте кнопку.");
			Возврат; 
		КонецЕсли;
	КонецЦикла;	
	
	ВключитьУпаковкиНаСервере();
	
КонецПроцедуры
&НаСервере
Процедура ВключитьУпаковкиНаСервере()
	
	Если ЗначениеЗаполнено(Объект.Ссылка) Тогда
		ОрдерОбъект = Объект.Ссылка.ПолучитьОбъект();
	Иначе
		ОрдерОбъект = РеквизитФормыВЗначение("Объект");
	КонецЕсли;
	
	МассивОбработанных = Новый Массив; 
	
	Для Каждого СтрокаТЧ Из ОрдерОбъект.Товары Цикл
		СсылкаНоменклатура = СтрокаТЧ.Номенклатура;
		Если Не ЗначениеЗаполнено(СсылкаНоменклатура) ИЛИ СсылкаНоменклатура.ЭтоГруппа Тогда
			Продолжить;
		КонецЕсли;
		
		Если МассивОбработанных.Найти(СсылкаНоменклатура) <> Неопределено Тогда
			Продолжить;
		КонецЕсли;
		МассивОбработанных.Добавить(СсылкаНоменклатура);
		
		ИмяЕдиницы = СокрЛП(СсылкаНоменклатура.ЕдиницаИзмерения.Наименование);
		НаборДляЗаписи = Справочники.НаборыУпаковок.НайтиПоНаименованию(ИмяЕдиницы);
		
		Если Не ЗначениеЗаполнено(НаборДляЗаписи) Тогда
			Продолжить;
		КонецЕсли;
		
		Если СсылкаНоменклатура.ИспользоватьУпаковки = Ложь ИЛИ СсылкаНоменклатура.НаборУпаковок <> НаборДляЗаписи Тогда
			ОбъектНом = СсылкаНоменклатура.ПолучитьОбъект();
			ОбъектНом.ИспользоватьУпаковки = Истина;
			ОбъектНом.НаборУпаковок = НаборДляЗаписи;
			ОбъектНом.ОбменДанными.Загрузка = Истина;
			Попытка ОбъектНом.Записать(); Исключение КонецПопытки;
		КонецЕсли;
	КонецЦикла;
	
	МассивНаборов = Новый Массив;
	Для Каждого СтрокаТЧ Из ОрдерОбъект.Товары Цикл
		Набор = СтрокаТЧ.Номенклатура.НаборУпаковок;
		Если ЗначениеЗаполнено(Набор) И МассивНаборов.Найти(Набор) = Неопределено Тогда
			МассивНаборов.Добавить(Набор);
		КонецЕсли;
	КонецЦикла;
	
	Если МассивНаборов.Количество() > 0 Тогда
		Запрос = Новый Запрос;
		Запрос.Текст = 
			"ВЫБРАТЬ
			|	УпаковкиЕдиницыИзмерения.Владелец КАК Набор,
			|	УпаковкиЕдиницыИзмерения.Наименование КАК НаименованиеУпаковки,
			|	МИНИМУМ(УпаковкиЕдиницыИзмерения.Ссылка) КАК Упаковка
			|ИЗ
			|	Справочник.УпаковкиЕдиницыИзмерения КАК УпаковкиЕдиницыИзмерения
			|ГДЕ
			|	УпаковкиЕдиницыИзмерения.Владелец В(&МассивНаборов)
			|	И УпаковкиЕдиницыИзмерения.ПометкаУдаления = ЛОЖЬ
			|СГРУППИРОВАТЬ ПО
			|	УпаковкиЕдиницыИзмерения.Владелец,
			|	УпаковкиЕдиницыИзмерения.Наименование";
			
		Запрос.УстановитьПараметр("МассивНаборов", МассивНаборов);
		ТаблицаУпаковок = Запрос.Выполнить().Выгрузить();
		
		Для Каждого СтрокаТЧ Из ОрдерОбъект.Товары Цикл
			Если Не ЗначениеЗаполнено(СтрокаТЧ.Упаковка) И ЗначениеЗаполнено(СтрокаТЧ.Номенклатура.НаборУпаковок) Тогда
				ПараметрыПоиска = Новый Структура("Набор, НаименованиеУпаковки", СтрокаТЧ.Номенклатура.НаборУпаковок, СтрокаТЧ.Номенклатура.ЕдиницаИзмерения.Наименование);
				НайденныеСтроки = ТаблицаУпаковок.НайтиСтроки(ПараметрыПоиска);
				
				Если НайденныеСтроки.Количество() > 0 И ЗначениеЗаполнено(НайденныеСтроки[0].Упаковка) Тогда
					СтрокаТЧ.Упаковка = НайденныеСтроки[0].Упаковка;
					Попытка СтрокаТЧ.КоличествоУпаковок = СтрокаТЧ.Количество; Исключение КонецПопытки;
				КонецЕсли;
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
	ОрдерОбъект.ОбменДанными.Загрузка = Истина; 
	ОрдерОбъект.Записать(РежимЗаписиДокумента.Запись);
	
	ЗначениеВРеквизитФормы(ОрдерОбъект, "Объект");
	
КонецПроцедуры

Ввод остатков на рабочей базе несколько лет спустя

Реализовать адресное хранение остатков на новой, чистой и девственной базе - задача несложная. Но наша организация успешно работала в ERP уже несколько лет, и вела складской учет. Просто включить адресное хранение и откинуться на спинку кресла нельзя, ведь весь учтенный на складе товар отсутствует в складских регистрах системы адресного хранения. На данном этапе требуется зафиксировать точку старта работы по новой логике, и для этого в системе 1С:ERP есть специальный документ "Ввод начальных остатков товаров после перехода на адресную систему хранения".

Идея простая: выгрузить текущие остатки организации в этот документ, указать для них дефолтную ячейку (например, "Зону приемки", чтобы склад хотя бы начал видеть товар на балансе), нажать кнопку "Провести" и пойти пить кофе, однако при попытке проведения документ выплюнул простыню ошибок - 1С наотрез отказалась распределять товар по ячейкам. Причина была всё та же: упаковки. Поскольку база велась несколько лет без адресной системы хранения, у 99% старых номенклатурных карточек использование упаковок было отключено. А адресный склад, как мы уже поняли, не умеет работать с базовыми единицами хранения - ему требуются упаковки. Механика была аналогична нашей автоматизации в ордерах, но масштабирована под большой объем данных перехода - табличная часть состояла из нескольких тысяч позиций.

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

Финансы против адресного хранения - как мы потрепали нервы бухгалтерии ордерным складом, но потом извинились.

Учитывая тот факт, что до включения адресного хранения компания жила несколько лет, и отгрузка товара со склада организации проводилась буквально одной кнопкой - "Провести и закрыть", то после включения данный бизнес-процесс значительно усложнился.

Теперь проведение финансового документа - это лишь распоряжение для склада. Чтобы товар реально уехал, нужно:

  • Зайти в рабочее место кладовщика;
  • Создать на основании реализации "Расходный ордер";
  • На основании ордера создать "Задание на отбор";
  • Кладовщик должен выполнить отбор (как физически, так и в системе);
  • Перевести документ отбора в статус "Выполнено без ошибок";
  • Перевести ордер в статус "К отгрузке", а затем "Отгружен".

Для бухгалтерии и менеджеров это превратилось в бесконечный кликомарафон, а рабочие чаты просто взорвались от гнева.

Мы поняли: если не вернуть людям их любимую одну кнопку, проект автоматизации просто саботируют. Нам нужно было извиниться перед юзерами единственным понятным для них способом - дать им инструмент автоматизации, который сделает всю цепочку за них. По одной кнопке. Прямо из ордера.

В Приходном ордере (Приемка): бухгалтер или кладовщик просто нажимает кнопку. Скрипт проверяет карточки номенклатуры, включает упаковки (о чем мы писалии чуть выше), а затем автоматически создает Задание на размещение. Программа сама берет товар из транзитной "Зоны приемки" и перемещает его в выбранную пару мгновениями ранее в диалоговом окне ячейку. Ордер сам переключается в статус "Принят". Для пользователя это заняло две секунды и один клик.

&НаСервере
Процедура СформироватьРазмещениеНаСервере(ЦелеваяЯчейкаРазмещения)
	
	ОрдерОбъект = РеквизитФормыВЗначение("Объект");
	
	ЯчейкаПриемкиФакт = ОрдерОбъект.ЗонаПриемки;
	Если Не ЗначениеЗаполнено(ЯчейкаПриемкиФакт) Тогда
		ВызватьИсключение "В Приходном ордере не заполнена ""Зона приемки""!";
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(ЦелеваяЯчейкаРазмещения) Тогда
		ВызватьИсключение "Не выбрана целевая ячейка для размещения товаров!";
	КонецЕсли;
	
	ЗаданиеОбъект = Документы.ОтборРазмещениеТоваров.СоздатьДокумент(); 
	ЗаданиеОбъект.ВидОперации = Перечисления.ВидыОперацийОтбораРазмещенияТоваров.Размещение; 
	ЗаданиеОбъект.Склад       = ОрдерОбъект.Склад;
	ЗаданиеОбъект.Дата        = ОрдерОбъект.Дата; 
	ЗаданиеОбъект.ЗонаПриемки = ЯчейкаПриемкиФакт; 
	
	ЗаданиеОбъект.Комментарий = "Создано автоматически на основании документа: Приходный ордер на товары " 
								+ ОрдерОбъект.Номер + " от " + Формат(ОрдерОбъект.Дата, "ДЛФ=D"); 
								
	Для Каждого СтрОрдера Из ОрдерОбъект.Товары Цикл
		
		СтрОтбор = ЗаданиеОбъект.ТоварыОтбор.Добавить();
		СтрОтбор.Номенклатура     = СтрОрдера.Номенклатура;
		СтрОтбор.Характеристика   = СтрОрдера.Характеристика;
		СтрОтбор.Серия            = СтрОрдера.Серия;
		СтрОтбор.Назначение       = СтрОрдера.Назначение;
		СтрОтбор.Упаковка         = СтрОрдера.Упаковка;
		СтрОтбор.Ячейка           = ЯчейкаПриемкиФакт; 
		СтрОтбор.Количество       = СтрОрдера.Количество;
		СтрОтбор.КоличествоУпаковок = СтрОрдера.КоличествоУпаковок;
		СтрОтбор.КоличествоОтобрано = СтрОрдера.Количество;
		СтрОтбор.КоличествоУпаковокОтобрано = СтрОрдера.КоличествоУпаковок;
		
		СтрРазмещение = ЗаданиеОбъект.ТоварыРазмещение.Добавить();
		СтрРазмещение.Номенклатура     = СтрОрдера.Номенклатура;
		СтрРазмещение.Характеристика   = СтрОрдера.Характеристика;
		СтрРазмещение.Серия            = СтрОрдера.Серия;
		СтрРазмещение.Назначение       = СтрОрдера.Назначение;
		СтрРазмещение.Упаковка         = СтрОрдера.Упаковка;
		СтрРазмещение.Ячейка           = ЦелеваяЯчейкаРазмещения; 
		СтрРазмещение.Количество       = СтрОрдера.Количество;
		СтрРазмещение.КоличествоУпаковок = СтрОрдера.КоличествоУпаковок;
		СтрРазмещение.КоличествоРазмещено = СтрОрдера.Количество;
		СтрРазмещение.КоличествоУпаковокРазмещено = СтрОрдера.КоличествоУпаковок;
		
	КонецЦикла;
	
	ЗаданиеОбъект.Статус = Перечисления.СтатусыОтборовРазмещенийТоваров.ВыполненоБезОшибок;
	
	Попытка
		ЗаданиеОбъект.Записать(РежимЗаписиДокумента.Проведение);
	Исключение
		ВызватьИсключение "Ошибка при проведении Задания на размещение! Ошибка: " + ОписаниеОшибки();
	КонецПопытки;
	
	ОрдерДляЗакрытия = ОрдерОбъект.Ссылка.ПолучитьОбъект();
	ОрдерДляЗакрытия.Статус = Перечисления.СтатусыПриходныхОрдеров.Принят;
	
	ОрдерДляЗакрытия.Комментарий = "Автоматически сформировано складское задание на размещение № " + ЗаданиеОбъект.Номер;
	
	Попытка
		ОрдерДляЗакрытия.Записать(РежимЗаписиДокумента.Проведение);
	Исключение
		ВызватьИсключение "Ошибка при финальном проведении Приходного ордера! Ошибка: " + ОписаниеОшибки();
	КонецПопытки;
	
	ЗначениеВРеквизитФормы(ОрдерДляЗакрытия, "Объект");
	
КонецПроцедуры

В Расходном ордере (Отгрузка): тут мы написали свой собственный мини-WMS движок. По нажатию кнопки система делает прямой запрос к регистру ТоварыВЯчейках, анализирует остатки на складе (исключая транзитные зоны отгрузки и приемки), распределяет списываемое количество по ячейкам хранения по убыванию остатка, сама создает документ Отбор размещение товаров со статусом "Выполнено без ошибок" и переводит ордер в статус "Отгружен".

&НаСервере
Процедура СформироватьОтборНаСервере()
    
    ОрдерОбъект = РеквизитФормыВЗначение("Объект");
    ЯчейкаОтгрузкиФакт = ОрдерОбъект.ЗонаОтгрузки;
    
    Если Не ЗначениеЗаполнено(ЯчейкаОтгрузкиФакт) Тогда
        ВызватьИсключение "В Расходном ордере не заполнена «Зона отгрузки»! Заполните её перед отгрузкой.";
    КонецЕсли;
    
    Если ОрдерОбъект.Статус = Перечисления.СтатусыРасходныхОрдеров.Подготовлен Тогда
        ОрдерОбъект.Статус = Перечисления.СтатусыРасходныхОрдеров.КОтбору;
        ОрдерОбъект.Записать(РежимЗаписиДокумента.Проведение);
    КонецЕсли;
    
    ЗаданиеОбъект = Документы.ОтборРазмещениеТоваров.СоздатьДокумент();
    ЗаданиеОбъект.ВидОперации 			  = Перечисления.ВидыОперацийОтбораРазмещенияТоваров.Отбор; 
    ЗаданиеОбъект.Склад       			  = ОрдерОбъект.Склад;
    ЗаданиеОбъект.ЗонаОтгрузки 			  = ЯчейкаОтгрузкиФакт; 
    ЗаданиеОбъект.Распоряжение 			  = ОрдерОбъект.Ссылка;
    ЗаданиеОбъект.Дата        			  = ОрдерОбъект.Дата;
	ЗаданиеОбъект.Исполнитель			  = ПользователиСлужебный.АвторизованныйПользователь();
	ЗаданиеОбъект.Ответственный 		  = ПользователиСлужебный.АвторизованныйПользователь();
	ЗаданиеОбъект.ДатаНачалаВыполнения    = ОрдерОбъект.Дата;
	ЗаданиеОбъект.ДатаОкончанияВыполнения = ОрдерОбъект.Дата;
	
    МассивНоменклатуры = ОрдерОбъект.ОтгружаемыеТовары.ВыгрузитьКолонку("Номенклатура");
    
    ЗапросОстатков = Новый Запрос;
    ЗапросОстатков.Текст = 
        "ВЫБРАТЬ
        |    ТоварыВЯчейках.Номенклатура,
        |    ТоварыВЯчейках.Характеристика,
        |    ТоварыВЯчейках.Назначение,
        |    ТоварыВЯчейках.Серия,
        |    ТоварыВЯчейках.Ячейка,
        |    ТоварыВЯчейках.ВНаличииОстаток КАК ОстатокВЯчейке
        |ИЗ
        |    РегистрНакопления.ТоварыВЯчейках.Остатки(
        |        ,
        |        Ячейка.Владелец = &Склад 
        |        И Номенклатура В (&МассивНоменклатуры)
        |        И Ячейка.ТипСкладскойЯчейки <> ЗНАЧЕНИЕ(Перечисление.ТипыСкладскихЯчеек.Приемка)
        |        И Ячейка.ТипСкладскойЯчейки <> ЗНАЧЕНИЕ(Перечисление.ТипыСкладскихЯчеек.Отгрузка)
        |    ) КАК ТоварыВЯчейках
        |ГДЕ
        |    ТоварыВЯчейках.ВНаличииОстаток > 0
        |УПОРЯДОЧИТЬ ПО
        |    ОстатокВЯчейке УБЫВ";
        
    ЗапросОстатков.УстановитьПараметр("Склад", ОрдерОбъект.Склад);
    ЗапросОстатков.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
    ТаблицаЯчеек = ЗапросОстатков.Выполнить().Выгрузить();
    
    ТекстОшибкиНехватки = ""; 
    
    Для Каждого СтрОрдера Из ОрдерОбъект.ОтгружаемыеТовары Цикл
        ОсталосьСобрать = СтрОрдера.Количество;
        
        ПараметрыОтбораСтрок = Новый Структура;
        ПараметрыОтбораСтрок.Вставить("Номенклатура", СтрОрдера.Номенклатура);
        ПараметрыОтбораСтрок.Вставить("Назначение",   СтрОрдера.Назначение);
        
        СтрокиСТоваром = ТаблицаЯчеек.НайтиСтроки(ПараметрыОтбораСтрок);
        
        Для Каждого СтрЯчейки Из СтрокиСТоваром Цикл
            Если ОсталосьСобрать <= 0 Тогда Прервать; КонецЕсли;
            Если СтрЯчейки.ОстатокВЯчейке <= 0 Тогда Продолжить; КонецЕсли;
            
            БеремИзЯчейки = Мин(ОсталосьСобрать, СтрЯчейки.ОстатокВЯчейке);
            
            Если СтрОрдера.Количество = 0 Тогда БеремУпаковок = 0;
            Иначе БеремУпаковок = (БеремИзЯчейки / СтрОрдера.Количество) * СтрОрдера.КоличествоУпаковок; КонецЕсли;
            
            СтрОтбор = ЗаданиеОбъект.ТоварыОтбор.Добавить();
            ЗаполнитьЗначенияСвойств(СтрОтбор, СтрОрдера);
            СтрОтбор.Ячейка = СтрЯчейки.Ячейка; 
            СтрОтбор.Количество = БеремИзЯчейки;
            СтрОтбор.КоличествоУпаковок = БеремУпаковок; 
            СтрОтбор.КоличествоОтобрано = БеремИзЯчейки;
            СтрОтбор.КоличествоУпаковокОтобрано = БеремУпаковок;
            
            СтрРазмещение = ЗаданиеОбъект.ТоварыРазмещение.Добавить();
            ЗаполнитьЗначенияСвойств(СтрРазмещение, СтрОрдера);
            СтрРазмещение.Ячейка = ЯчейкаОтгрузкиФакт; 
            СтрРазмещение.Количество = БеремИзЯчейки;
            СтрРазмещение.КоличествоУпаковок = БеремУпаковок;
            СтрРазмещение.КоличествоРазмещено = БеремИзЯчейки;
            СтрРазмещение.КоличествоУпаковокРазмещено = БеремУпаковок;
            
            ОсталосьСобрать = ОсталосьСобрать - БеремИзЯчейки;
            СтрЯчейки.ОстатокВЯчейке = СтрЯчейки.ОстатокВЯчейке - БеремИзЯчейки;
        КонецЦикла;
        
        Если ОсталосьСобрать > 0 Тогда
            ТекстОшибкиНехватки = ТекстОшибкиНехватки + Символы.ПС + "— " + СтрОрдера.Номенклатура + " (не хватает: " + ОсталосьСобрать + ")";
        КонецЕсли;
    КонецЦикла;
    
    Если ЗначениеЗаполнено(ТекстОшибкиНехватки) Тогда
        ВызватьИсключение "Следующие товары не найдены в ячейках хранения (возможно, они еще лежат в Зоне приемки):" 
            + ТекстОшибкиНехватки + Символы.ПС + Символы.ПС 
            + "Сначала выполните размещение этих товаров на стеллажи!";
    КонецЕсли;
    
    ЗаданиеОбъект.Статус = Перечисления.СтатусыОтборовРазмещенийТоваров.ВыполненоБезОшибок; 
    ЗаданиеОбъект.Записать(РежимЗаписиДокумента.Проведение);
    
    Для Каждого СтрОрдера Из ОрдерОбъект.ОтгружаемыеТовары Цикл
        СтрОрдера.Действие = Перечисления.ДействияСоСтрокамиОрдеровНаОтгрузку.Отгрузить; 
    КонецЦикла;
    
    ОрдерОбъект.Статус = Перечисления.СтатусыРасходныхОрдеров.Отгружен; 
    ОрдерОбъект.Записать(РежимЗаписиДокумента.Проведение);
    
    ЗначениеВРеквизитФормы(ОрдерОбъект, "Объект");

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

Когда мы попытались встроить автоматизацию в лоб, типовые модули начали намертво блокировать таблицы: один ордер ждал проведения задания, в которое ERP пыталась впихнуть строки из совершенно другого документа.

Краткие итоги:

  • Склад перешел на адресное хранение без остановки отгрузок ни на один день;
  • Бухгалтерия получила обратно свою бизнес-процесс в один клик и перестали ненавидеть складской учет;
  • Сотрудники склада видят в отчетах четкие адреса и остатки, а не ищут товар интуитивно.

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