Типовое внедрение адресного хранения в 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 пыталась впихнуть строки из совершенно другого документа.
Краткие итоги:
- Склад перешел на адресное хранение без остановки отгрузок ни на один день;
- Бухгалтерия получила обратно свою бизнес-процесс в один клик и перестали ненавидеть складской учет;
- Сотрудники склада видят в отчетах четкие адреса и остатки, а не ищут товар интуитивно.
Да, мы нарушили каноническое разделение складского и финансового учета, ведь мы обошли механизмы консолидации заданий по ролям, а также внедрили "костыли" в виде безразмерных ячеек и фиктивных упаковок. Но иногда, чтобы система реально заработала и принесла пользу бизнесу, нужно проявлять инженерную наглость.