В этой статье я расскажу, как даже начинающий разработчик может быстро и без затруднений реализовать подсистему для складского учета.
Конечно, это касается не только складского учета - в 1С можно легко создать учет чего угодно, всё, что необходимо, уже есть в платформе.
Я буду делать справочный адресный склад на демо-версии конфигурации Библиотека Стандартных Подсистем (Демонстрационная конфигурация "Библиотека стандартных подсистем", редакция 3.1 (3.1.8.250)), платформа 1С:Предприятие 8.3 (8.3.23.1596) Версия для разработчиков.
На самом деле, никаких требований ни к конфигурации ни к платформе нет, то же самое можно гораздо проще делать, например, в 1С: Бухгалтерии 3. Но конечно, будет лучше, если в конфигурации уже есть справочник товаров.
В демо-конфигурации БСП есть справочники товаров, организаций, складов и контрагентов, чего более чем достаточно для реализации простейшей складской функциональности.
Я буду работать в расширении, но, конечно, обычно проще и надёжнее разрабатывать новую функциональность непосредственно в самой конфигурации.
Итак, первым делом добавляем собственно расширение. Отнеситесь серьезно к заполнению префикса - с него будут начинаться названия всех ваших объектов!
Чтобы не ходить два раза, сразу заимствуем в расширение необходимые объекты:
Самое важное - это справочники складов (здесь это _ДемоМестаХранения) и товаров (_ДемоНоменклатура). Справочник организаций может и не понадобится - это зависит от потребностей. Также я взял справочник Пользователи для того, чтобы заполнять ответственных за складские операции.
Добавим нужные подсистемы и роли. Я ограничусь одной подсистемой (у меня очень ограниченная функциональность) и одной ролью:
В подсистему я сразу добавлю справочники складов и товаров - просто чтобы они отображались в командном интерфейсе.
Адресный склад по сравнению с неадресным имеет, по сути, ровно одну особенность: адреса. Адреса на адресном складе - это способ быстрой навигации при поиске товаров. Все остальные плюсы и минусы адресных складов проистекают именно из этой особенности.
В нашей конфигурации склады уже есть, но они не имеют внутренней структуры, собственно, адресов. Поэтому просто добавим соответствующий справочник. Чтобы не путать адреса на складе с адресами, например, контрагентов или физических лиц, назовем справочник Ячейки, потому что конечный элемент адреса - это ячейка.
Адреса на адресном складе обычно имеют структуру. Как правило, это помещения, ряды, стеллажи, полки. Так как это обычно иерархическая структура, то и для её представления воспользуемся иерархией справочника.
Адрес ячейки обычно выглядит как последовательность адресов всех вышестоящих элементов, например, если склад состоит из рядов, которые называются латинскими буквами, в которых стоят стеллажи с номерами, на стеллажах полки, пронумерованные снизу вверх и, наконец, полки поделены на нумерованные ячейки, то адрес ячейки будет
Ряд-стеллаж-полка-ячейка, например, А-1-1-1, X-15-3-6
Пусть у справочника ячеек будет владелец - справочник складов.
Теперь у нас есть вся справочная информация, и можно приступить, собственно, к реализации складских движений. Складские движения могут быть внешние, это поступление на склад и убытие со склада, и внутренние - перемещения товаров между ячейками.
Прежде всего, товар на склад должен поступить, поэтому создаем Поступление товара:
Так как у меня будет справочный адресный склад, то поступление товаров - это справочник.
Основные реквизиты поступления - это Организация, Склад, Контрагент, Договор и список товаров с количеством и суммой. Ячейки будем вводить в табличной части Товары. Если окажется, что один товар нужно разместить в нескольких ячейках - будем просто разбивать строки. Вопросы с НДС, скидками и тому подобным пропустим - эти реквизиты нужны не всем, ведь некоторые организации работают без НДС.
Также в шапку нужно добавить дату, когда произошла хозяйственная операция и признак, что элемент справочника проведен:
Добавляем простейшую форму с обработчиками необходимых событий. Раз уж у нас есть в документе цена и сумма, нужно добавить пересчет одного в другое, и можно перейти в режим 1С: Предприятия, чтобы попробовать заполнить какие-то тестовые данные.
Всё заполняется как задумано, теперь нужно сделать, чтобы наш справочник проводился. Нужно это для того, чтобы делать движения в регистры и получать по ним отчеты.
Добавим регистр Движения по ячейкам (платформа 8.3.23.1596 позволяет выбирать типом измерений регистра справочники, добавленные в расширении, ура!):
Пишем обработку проведения и обработку удаления движений.
Процедура ПриЗаписи(Отказ)
Если ПометкаУдаления Тогда
ОбработкаУдаленияПроведения()
Иначе
ОбработкаПроведения()
КонецЕсли;
КонецПроцедуры
Процедура ОбработкаПроведения()
УдалитьДвижения();
НаборЗаписей = РегистрыСведений.КАЕ_ДвиженияПоЯчейкам.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Операция.Установить(Ссылка);
Для Каждого СтрокаТовары Из Товары Цикл
Запись = НаборЗаписей.Добавить();
Запись.Склад = Склад;
Запись.Номенклатура = СтрокаТовары.Номенклатура;
Запись.Строка = СтрокаТовары.НомерСтроки;
Запись.Операция = Ссылка;
Запись.Ячейка = СтрокаТовары.Ячейка;
Запись.Количество = СтрокаТовары.Количество;
КонецЦикла;
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Процедура ОбработкаУдаленияПроведения()
УдалитьДвижения()
КонецПроцедуры
Процедура ПередЗаписью(Отказ)
Если Не ЗначениеЗаполнено(Дата) Тогда
Дата = ТекущаяДата();
КонецЕсли;
Если Не ЗначениеЗаполнено(Ответственный) Тогда
Ответственный = Пользователи.ТекущийПользователь();
КонецЕсли;
КонецПроцедуры
Процедура УдалитьДвижения()
НаборЗаписей = РегистрыСведений.КАЕ_ДвиженияПоЯчейкам.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Операция.Установить(Ссылка);
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Пробуем провести справочник - движения получились!
Конечно, нам понадобятся остатки. Остатки должны храниться в регистре остатков, создадим и его:
Было бы глупо, если бы мы в 21 веке при проведении справочника отдельно записывали остатки в регистр. Поэтому просто добавим событие ПередЗаписью регистра движений: будем прибавлять к остатку ровно столько, сколько записалось в движениях:
Процедура ПередЗаписью(Отказ, Замещение)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КАЕ_ДвиженияПоЯчейкам.Склад КАК Склад,
| КАЕ_ДвиженияПоЯчейкам.Номенклатура КАК Номенклатура,
| КАЕ_ДвиженияПоЯчейкам.Ячейка КАК Ячейка,
| СУММА(КАЕ_ДвиженияПоЯчейкам.Количество) КАК Количество
|ИЗ
| РегистрСведений.КАЕ_ДвиженияПоЯчейкам КАК КАЕ_ДвиженияПоЯчейкам
|ГДЕ
| &Условия
|
|СГРУППИРОВАТЬ ПО
| КАЕ_ДвиженияПоЯчейкам.Склад,
| КАЕ_ДвиженияПоЯчейкам.Номенклатура,
| КАЕ_ДвиженияПоЯчейкам.Ячейка";
Условия = "";
Для Каждого ЭлементОтбора Из Отбор Цикл
Если ЭлементОтбора.Использование Тогда
Условия = Условия + "
| И " + ЭлементОтбора.Имя + " = &" + ЭлементОтбора.Имя;
Запрос.УстановитьПараметр(ЭлементОтбора.Имя, ЭлементОтбора.Значение);
КонецЕсли;
КонецЦикла;
Запрос.Текст = СтрЗаменить(Запрос.Текст, "&Условия", "ИСТИНА " + Условия);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НаборЗаписей = РегистрыСведений.КАЕ_ОстаткиВЯчейках.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Склад.Установить(Выборка.Склад);
НаборЗаписей.Отбор.Номенклатура.Установить(Выборка.Номенклатура);
НаборЗаписей.Отбор.Ячейка.Установить(Выборка.Ячейка);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Запись.Количество = Запись.Количество - Выборка.Количество;
КонецЦикла;
НаборЗаписей.Записать(Истина);
КонецЦикла;
Для Каждого ТекущаяЗапись Из ЭтотОбъект Цикл
НаборЗаписей = РегистрыСведений.КАЕ_ОстаткиВЯчейках.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Склад.Установить(ТекущаяЗапись.Склад);
НаборЗаписей.Отбор.Номенклатура.Установить(ТекущаяЗапись.Номенклатура);
НаборЗаписей.Отбор.Ячейка.Установить(ТекущаяЗапись.Ячейка);
НаборЗаписей.Прочитать();
Если НаборЗаписей.Количество() = 0 Тогда
Запись = НаборЗаписей.Добавить();
Запись.Склад = ТекущаяЗапись.Склад;
Запись.Номенклатура = ТекущаяЗапись.Номенклатура;
Запись.Ячейка = ТекущаяЗапись.Ячейка;
Иначе
Запись = НаборЗаписей[0];
КонецЕсли;
Запись.Количество = Запись.Количество + ТекущаяЗапись.Количество;
НаборЗаписей.Записать(Истина);
КонецЦикла;
КонецПроцедуры
Для теста проводим второй элемент справочника, пробуем пометить на удаление, опять провести и так далее:
Видно, что остатки меняются:
Аналогично добавляем расходные документы и перемещения. Расход можно представить в виде отрицательного количества в регристре.
Реализацию товара на первых порах можно сделать просто копированием поступления, а с перемещением, конечно, придётся потрудиться.
Перемещение списывает товар из одного места и приходует в другое, поэтому в шапке должно быть два склада, а в табличной части две ячейки - отправитель и получатель. Как я уже говорил, если есть необходимость один и тот же артикул отобрать или разместить в разные ячейки, достаточно разбить одну строку на несколько:
Не забываем удалить лишние реквизиты и обработчики!
Далее, для удобства принято помещать однородные операции в журналы, создаем его:
Запись в этот журнал можно делать в обработчике ПриЗаписи, если разрабатываем в основной конфигурации, или позволяет версия платформы, то лучше делать это в подписке (платформа 8.3.23.1596 позволяет добавлять подписки в расширении, наконец-то!).
Процедура КАЕ_СкладскаяОперацияПриЗаписиПриЗаписи(Источник, Отказ) Экспорт
НаборЗаписей = РегистрыСведений.КАЕ_СправочныйСклад.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Операция.Установить(Ссылка);
Запись = НаборЗаписей.Добавить();
Запись.Операция = Источник.Ссылка;
Запись.Дата = Источник.Дата;
Запись.Организация = Источник.Организация;
НаборЗаписей.Записать(Истина);
КонецПроцедуры
В регистре нужно заполнить поля Отправитель и Получатель, но в разных видах операций это разные поля, что делать? Воспользуемся, скажем так, полиморфизмом, и сделаем в модуле каждого справочника функцию для получения этих полей:
// в Перемещении:
Функция Отправитель() Экспорт
Возврат Склад;
КонецФункции
Функция Получатель() Экспорт
Возврат СкладПолучатель;
КонецФункции
// в Реализации:
Функция Отправитель() Экспорт
Возврат Склад;
КонецФункции
Функция Получатель() Экспорт
Возврат Контрагент;
КонецФункции
// в Поступлении
Функция Отправитель() Экспорт
Возврат Контрагент;
КонецФункции
Функция Получатель() Экспорт
Возврат Склад;
КонецФункции
И теперь можно единообразно получать эти поля. Языки разные, принципы везде одни:
Процедура КАЕ_СкладскаяОперацияПриЗаписиПриЗаписи(Источник, Отказ) Экспорт
НаборЗаписей = РегистрыСведений.КАЕ_СправочныйСклад.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Операция.Установить(Источник.Ссылка);
Запись = НаборЗаписей.Добавить();
Запись.Операция = Источник.Ссылка;
Запись.Дата = Источник.Дата;
Запись.Организация = Источник.Организация;
Запись.Получатель = Источник.Получатель();
Запись.Отправитель = Источник.Отправитель();
НаборЗаписей.Записать(Истина);
КонецПроцедуры
Конечно, не забываем добавлять объекты в подсистемы и в командный интерфейс.
Кстати, если объект не должен отображаться в клиенте, то всё равно нужно создать для таких объектов подсистему, но не включать её в командный интерфейс - это нужно как минимум для отбора в дереве метаданных, например, полезно при сравнении/объединении конфигураций.
Перепроводим справочники, журнал выглядит идеально:
Но вот незадача - вместо наших складских операций из журнала открываются его записи:
Чтобы это исправить, нужно создать форму списка и назначить обработчик. Я буду использовать ПоказатьЗначение(), можно использовать ОткрытьФорму().
Ну и, конечно, нужен отчет, ведь данные в учетную систему вводятся не просто так, а для того, чтобы получать их оттуда в консолидированном виде. Создам простейшую оборотку на СКД. Остатки и обороты лежат в разных регистрах, поэтому в голову приходят три варианта:
1. два набора данных
2. один набор данных с запросом - полным соединением
3. один набор данных с запросом - объединением
Попробуем первый вариант:
В принципе, всё работает (я забыл указать ячейку-получатель в перемещении).
Что тут ещё нужно доработать:
1. Видно, что регистр остатков хранит только текущий остаток. Чтобы получить остаток на произвольный момент, придётся или отнять от этого остатка все движения от текущего момента до выбранного, либо вообще не использовать этот регистр, а просто складывать все движения от сотворения мира до текущего момента.
2. Нужно добавить печатные формы
3. В форму списка ячеек нужно добавить отбор по складу
В остальном получается, что за пару часов в 1С можно соорудить из подручных средств почти идеальную систему для адресного склада!
Приложенное расширение на платформе старше 8.3.23, вероятно, не заработает.
P.S. Всех с первым апреля, если вышло позже - извините.