В практической работе со стандартной конфигурацией «1С:Управление нашей фирмой» (1С:УНФ) часто возникает классическая задача разделения учета. Когда в одной информационной базе ведется учет по двум или более организациям, у каждой из которых есть свои собственные склады, пользователи часто совершают ошибки. Платформа по умолчанию позволяет выбрать абсолютно любой склад в документах поступления, реализации, перемещения или сборки, даже если этот склад логически и физически принадлежит другому юридическому лицу. Это приводит к пересортице, искажению себестоимости и ошибкам в оперативных отчетах.
В рамках этой статьи мы подробно разберем, как решить эту проблему изящным способом с помощью механизмов расширения конфигурации, не снимая основную конфигурацию с поддержки. Также мы затронем важный смежный вопрос: как правильно управлять временем проведения документов (на примере документа «Сборка запасов»), чтобы избежать коллизий с отрицательными остатками в течение дня.
Прежде чем приступать к написанию кода, проанализируем структуру метаданных конфигурации 1С:УНФ. В отличие от тяжелых ERP-систем, где склады могут иметь жесткую подчиненность организациям на уровне владельцев справочников, в УНФ используется справочник СтруктурныеЕдиницы. Этот справочник является универсальным: в нем хранятся как оптовые склады, так и розничные магазины, а также производственные подразделения компании.
Этот справочник не имеет стандартного иерархического владельца «Организации». Однако разработчики фирмы «1С» предусмотрели в нем реквизит Организация с типом ссылки на справочник Организации. Это дает нам полную техническую возможность связать склад с конкретным юридическим лицом. Если этот реквизит заполнен в карточке склада, мы можем использовать его для фильтрации в документах.
Самый правильный и соответствующий стандартам разработки «1С» способ ограничить выбор складов — использовать механизм **связей параметров выбора** (СвязиПараметровВыбора). Этот механизм заставляет платформу автоматически отслеживать изменение реквизита Организация на форме документа и мгновенно применять фильтр к списку выбора складов.
Поскольку мы решаем задачу через расширение, нам не нужно переписывать формы всех документов вручную. Мы можем добавить нужные формы документов в наше расширение и дописать небольшой код в обработчике события ПриСозданииНаСервере. Выясним причину, почему это лучше делать программно: свойства реквизитов в дереве метаданных расширения заблокированы для изменения, поэтому динамическое добавление связей через код — это единственный надежный путь.
Рассмотрим по шагам программную реализацию этого метода в расширении для формы документа (например, «Заказ покупателя» или «Приходная накладная»):
&После("ПриСозданииНаСервере")
Процедура ИН_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
// Создаем новый массив для хранения связей параметров выбора
НовыеСвязи = Новый Массив;
// Переносим существующие связи, чтобы не сломать типовой функционал
Для Каждого Связь Из Элементы.СтруктурнаяЕдиница.СвязиПараметровВыбора Цикл
НовыеСвязи.Добавить(Связь);
КонецЦикла;
// Добавляем новую связь: фильтруем склады по организации, указанной в документе
// Первый параметр — путь к реквизиту-фильтру в справочнике ("Отбор.Организация")
// Второй параметр — путь к реквизиту формы, откуда берется значение ("Объект.Организация")
НовыеСвязи.Добавить(Новый СвязьПараметраВыбора("Отбор.Организация", "Объект.Организация"));
// Перезаписываем связи параметров выбора элемента формы
Элементы.СтруктурнаяЕдиница.СвязиПараметровВыбора = Новый ФиксированныйМассив(НовыеСвязи);
КонецПроцедуры
Важное примечание: Имя элемента формы (в данном случае СтруктурнаяЕдиница) может отличаться в разных документах (например, Склад, СкладПолучатель, Подразделение). Перед написанием кода обязательно проверьте имя элемента в свойствах конкретной управляемой формы.
Этот метод прекрасен тем, что работает полностью на стороне платформы. Как только пользователь меняет организацию в шапке документа, платформа сама отправляет запрос к базе данных и обновляет список доступных для выбора складов, оставляя только те, у которых реквизит Организация совпадает с выбранной.
Рассмотрим более сложную ситуацию. Предположим, у нас есть организация «ООО ХХХ (Плюс)», для которой нужно жестко закрепить один конкретный склад (например, «Склад ХХХ (Плюс)»), а для других организаций оставить стандартное поведение или реализовать иную сложную логику. В этом случае стандартные связи параметров выбора могут не справиться, и нам потребуется использовать программную установку ПараметрыВыбора на клиенте в момент начала выбора.
Разберем пример кода, который перехватывает событие начала выбора склада на форме документа:
&НаКлиенте
Процедура ИН_СтруктурнаяЕдиницаНачалоВыбораПеред(Элемент, ДанныеВыбора, СтандартнаяОбработка)
// Проверяем заполненность организации и ее наименование
Если ЗначениеЗаполнено(Объект.Организация) И Объект.Организация.Наименование = "ООО ""ХХХ"" (ПЛЮС)" Тогда
// Отменяем стандартную обработку выбора, чтобы задать свои параметры
СтандартнаяОбработка = Ложь;
// Массив для хранения разрешенных складов
МассивРазрешенныхСкладов = Новый Массив;
// Получаем ссылку на конкретный склад (в реальной базе лучше использовать предопределенные элементы или настройки)
СкладСсылка = ПолучитьСкладПоНаименованию("Склад ХХХ (Плюс)");
Если ЗначениеЗаполнено(СкладСсылка) Тогда
МассивРазрешенныхСкладов.Добавить(СкладСсылка);
КонецЕсли;
// Формируем параметры отбора по ссылке
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Ссылка", Новый ФиксированныйМассив(МассивРазрешенныхСкладов));
// Формируем параметры открытия формы выбора
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Отбор", ПараметрыОтбора);
// Открываем форму выбора справочника с нашими параметрами отбора
ОткрытьФорму("Справочник.СтруктурныеЕдиницы.ФормаВыбора", ПараметрыФормы, Элемент);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьСкладПоНаименованию(ИмяСклада)
Возврат Справочники.СтруктурныеЕдиницы.НайтиПоНаименованию(ИмяСклада);
КонецФункции
Совет по лучшим практикам разработки: Поиск элементов справочника по наименованию (НайтиПоНаименованию) в программном коде считается крайне нежелательным методом ("плохим тоном"), так как пользователи могут случайно переименовать элемент в режиме предприятия, что приведет к неработоспособности кода. Вместо этого рекомендуется использовать дополнительные реквизиты (свойства), константы или регистры сведений для хранения настроек соответствия организаций и складов.
Вторая проблема, часто возникающая при параллельной работе нескольких складов — хронологическая последовательность проведения документов. Типичный пример: документ «Сборка запасов» проводится в течение дня. Если комплектующие поступили на склад в 12:00, а сборка готовой продукции была проведена «задним числом» или просто в 09:00 утра того же дня, система выдаст ошибку о нехватке запасов, так как на момент сборки комплектующих еще не было физически.
Чтобы решить эту проблему, разработчики часто пытаются принудительно установить время проведения таких документов на начало дня (00:00:00) или сделать документ «первым» в пределах суток.
Посмотрим на распространенную ошибку начинающих разработчиков: попытка изменить дату и время документа внутри процедур ПриСозданииНаСервере или через преобразование реквизита формы в объект с помощью РеквизитФормыВЗначение в процедуре ПриЗаписиНаСервере без обратного вызова ЗначениеВРеквизитФормы. Такое изменение либо затирается платформой при записи, либо вызывает ошибки блокировок.
Правильный способ управления датой и временем нового документа — использование предопределенного события формы **ПередЗаписьюНаСервере** или подписки на событие **ПередЗаписью** в модуле самого объекта. Разберем правильный вариант реализации на уровне формы документа в расширении:
&НаСервере
Процедура ИН_ПередЗаписьюНаСервереПеред(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Корректируем время только для новых, еще не записанных в базу данных документов
Если ТекущийОбъект.ЭтоНовый() Тогда
// Вариант А: Установка времени документа строго на начало дня (00:00:00)
ТекущийОбъект.Дата = НачалоДня(ТекущийОбъект.Дата);
// Вариант Б: Использование стандартного механизма платформы для позиционирования документа первым в дне
// Этот метод автоматически сдвигает документ в самую раннюю позицию в пределах указанной даты
ТекущийОбъект.УстановитьВремя(РежимАвтоВремя.Первым);
КонецЕсли;
КонецПроцедуры
В данном коде параметр ТекущийОбъект имеет тип ДокументОбъект.СборкаЗапасов. Любые изменения, внесенные в его свойства в процедуре ПередЗаписьюНаСервере, будут гарантированно и корректно записаны в базу данных платформой «1С:Предприятие» без необходимости дополнительных преобразований типов.
Использование метода РежимАвтоВремя.Первым является наиболее мягким решением, так как оно сохраняет логическую структуру документов и позволяет им гарантированно проводиться раньше расходных накладных и отчетов о розничных продажах, зарегистрированных в течение этого же дня.
Мы успешно решили две важные задачи автоматизации складского учета в 1С:УНФ с помощью механизмов расширений:
СвязиПараметровВыбора и динамическое переопределение параметров на клиенте.ПередЗаписьюНаСервере.Эти подходы позволяют сохранить типовую конфигурацию на полной поддержке, гарантируя легкое обновление системы в будущем и исключая человеческий фактор при ведении многофирменного учета.