Как сделать элементарный склад в 1С 8.3 с нуля и настроить контроль остатков?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

Создание простого складского учета — это базовая задача, с которой начинается путь любого разработчика в среде «1С:Предприятие 8.3». На первый взгляд задача кажется масштабной, но если разбить её на логические этапы, всё становится предельно понятным. В этой статье мы подробно разберем, как спроектировать метаданные для склада, как связать документы с регистрами накопления и как реализовать программный контроль остатков, чтобы пользователи не могли списать товар «в минус».

Этап 1. Проектирование структуры метаданных

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

  1. Справочники:
    • Номенклатура — здесь будет храниться список наших товаров или услуг.
    • Склады — справочник мест хранения. Даже если у вас сейчас один склад, эту аналитику необходимо заложить сразу, чтобы архитектуру не пришлось переписывать в будущем.
  2. Регистр накопления:
    • Создадим регистр накопления с именем ОстаткиТоваров. Это ключевой объект нашей системы.
    • Важно: Вид регистра обязательно должен быть установлен в значение Остатки (а не Обороты). Только этот вид регистра позволяет получать остатки товаров на любую контрольную дату.
    • Измерения: Номенклатура (тип: СправочникСсылка.Номенклатура) и Склад (тип: СправочникСсылка.Склады). Измерения — это разрезы учета, по которым мы будем группировать остатки.
    • Ресурсы: Количество (тип: Число, длина 15, точность 3). Ресурсы — это числовые показатели, которые будут суммироваться или вычитаться.
  3. Документы:
    • ПоступлениеТоваров (для оформления прихода на склад).
    • РеализацияТоваров (для оформления отпуска товаров со склада).
    • Оба документа должны содержать реквизит шапки Склад (тип: СправочникСсылка.Склады) и табличную часть Товары с реквизитами Номенклатура (тип: СправочникСсылка.Номенклатура) и Количество (тип: Число).

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

Этап 2. Программный контроль остатков товаров

Контроль отрицательных остатков — классическая задача платформы 1С. Существует два принципиально разных подхода к её решению: «старая» методика проведения и «новая» методика проведения. Рассмотрим обе схемы работы, проанализируем их плюсы и минусы и разберем готовый программный код.

Метод А. «Старая» методика проведения (Проверка до записи движений)

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

Рассмотрим по шагам логику работы этого механизма в обработчике ОбработкаПроведения модуля документа РеализацияТоваров:


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

Преимущества метода: Алгоритм интуитивно понятен новичкам. Мы сначала «думаем» (проверяем), а потом «делаем» (записываем).

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

Метод Б. «Новая» методика проведения (Проверка после записи движений)

Эта методика является современным стандартом разработки в среде 1С и используется во всех типовых решениях компании, а также строго требуется при сдаче экзамена «1С:Специалист». Смысл метода в том, что мы сначала безоговорочно записываем движения расхода в регистр накопления (допускаем временный «минус» в транзакции), а затем одним быстрым запросом проверяем, не ушли ли остатки товаров в минус. Если обнаруживается дефицит, мы отменяем транзакцию.

Проанализируем реализацию новой методики:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // 1. Включаем запись движений и устанавливаем автоматическую блокировку для изменения
    Движения.ОстаткиТоваров.Записывать = Истина;
    Движения.ОстаткиТоваров.БлокироватьДляИзменения = Истина;
    
    // 2. Сразу формируем движения расхода на основе табличной части документа
    Для Каждого СтрокаТовара Из Товары Цикл
        Движение = Движения.ОстаткиТоваров.ДобавитьРасход();
        Движение.Период = Дата;
        Движение.Склад = Склад;
        Движение.Номенклатура = СтрокаТовара.Номенклатура;
        Движение.Количество = СтрокаТовара.Количество;
    КонецЦикла;
    
    // 3. Записываем движения в базу данных
    Движения.Записать();
    
    // 4. Выполняем запрос для проверки наличия отрицательных остатков
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	РеализацияТоваровТовары.Номенклатура КАК Номенклатура,
        |	СУММА(РеализацияТоваровТовары.Количество) КАК КоличествоДокумент
        |ПОМЕСТИТЬ ВТ_ТоварыДокумента
        |ИЗ
        |	Документ.РеализацияТоваров.Товары КАК РеализацияТоваровТовары
        |ГДЕ
        |	РеализацияТоваровТовары.Ссылка = &Ссылка
        |ГРУППИРОВАТЬ ПО
        |	РеализацияТоваровТовары.Номенклатура
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ_ТоварыДокумента.Номенклатура КАК Номенклатура,
        |	ОстаткиТоваровОстатки.КоличествоОстаток КАК КоличествоОстаток
        |ИЗ
        |	ВТ_ТоварыДокумента КАК ВТ_ТоварыДокумента
        |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(&ГраницаПроведения, Склад = &Склад) КАК ОстаткиТоваровОстатки
        |		ПО ВТ_ТоварыДокумента.Номенклатура = ОстаткиТоваровОстатки.Номенклатура
        |ГДЕ
        |	ОстаткиТоваровОстатки.КоличествоОстаток < 0";
    
    // Используем объект Граница со свойством Включая, чтобы запрос увидел только что сделанные движения нашего документа
    ГраницаПроведения = Новый Граница(МоментВремени(), ВидГраницы.Включая);
    
    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    Запрос.УстановитьПараметр("ГраницаПроведения", ГраницаПроведения);
    Запрос.УстановитьПараметр("Склад", Склад);
    
    РезультатЗапроса = Запрос.Выполнить();
    
    // 5. Если запрос вернул строки с отрицательными остатками - отменяем проведение
    Если Не РезультатЗапроса.Пустой() Тогда
        
        ВыборкаОшибок = РезультатЗапроса.Выбрать();
        Пока ВыборкаОшибок.Следующий() Цикл
            
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = "Недостаточно товара " + ВыборкаОшибок.Номенклатура + 
                               " на складе " + Склад + 
                               ". Не хватает: " + (-ВыборкаОшибок.КоличествоОстаток) + " шт.";
            Сообщение.Сообщить();
            
            Отказ = Истина;
            
        КонецЦикла;
        
    КонецЕсли;
    
КонецПроцедуры

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

Этап 3. Создание отчета по остаткам на складе

Чтобы контролировать движение товаров, нам нужен отчет. Создадим отчет ОстаткиИОборотыТоваров на базе Системы компоновки данных (СКД):

  1. Создадим новый объект метаданных Отчет в конфигураторе и откроем основную схему компоновки данных.
  2. Добавим новый набор данных типа Запрос.
  3. В конструкторе запроса выберем виртуальную таблицу РегистрНакопления.ОстаткиТоваров.ОстаткиИОбороты. Эта таблица автоматически предоставляет готовые поля:
    • КоличествоНачальныйОстаток
    • КоличествоПриход
    • КоличествоРасход
    • КоличествоКонечныйОстаток
  4. На вкладке «Настройки» СКД добавим группировку по полю Склад, внутрь нее добавим группировку по полю Номенклатура, а в выбранные поля выведем начальный остаток, приход, расход и конечный остаток.

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

← На главную