Как реализовать контроль остатков при возврате оборудования от сотрудника на склад в 1С

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

В процессе разработки складского функционала на платформе 1С:Предприятие 8 часто возникает задача не просто зафиксировать поступление или выдачу ТМЦ, но и обеспечить жесткий контроль за тем, чтобы сотрудники не возвращали больше оборудования, чем им было выдано ранее. Рассмотрим подробно, как правильно спроектировать архитектуру регистров и реализовать программный контроль остатков в документах выдачи и возврата.

Проектирование архитектуры регистров накопления

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

  1. ОборудованиеНаСкладах — регистр остатков, который отвечает за общее количество ТМЦ на физических складах компании. Основные измерения: Склад, Оборудование.
  2. ОборудованиеВЭксплуатации (или РегистрХранения) — регистр остатков, который фиксирует, какое оборудование числится за конкретным подотчетным лицом. Основные измерения: Сотрудник, Оборудование.

Разберем логику работы документов в этой связке:

Реализация контроля остатков в документе «Выдача»

Перед тем как выдать монитор или ноутбук сотруднику, мы должны убедиться, что это оборудование физически есть на выбранном складе. Проанализируем стандартную методику контроля, реализованную в процедуре ОбработкаПроведения.


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

Реализация документа «Возврат» с контролем по сотруднику

Теперь перейдем к ключевому вопросу: как не позволить Ивану вернуть 5 мониторов, если мы выдавали ему только 2? Основная проблема кода, представленного в теме форума, заключалась в том, что в запросе к виртуальной таблице Остатки не передавался фильтр по измерению Сотрудник. Из-за этого система проверяла общий остаток оборудования у всех сотрудников сразу.

Рассмотрим правильный алгоритм проведения возврата с использованием «старой» методики (запись с последующей проверкой на отрицательный остаток), так как она наиболее наглядна для понимания процесса.

Шаг 1. Формирование движений расхода. Сначала мы записываем в регистр эксплуатации «Расход», как будто сотрудник уже вернул оборудование.

Шаг 2. Запись движений. Вызываем Движения.Записать(). В этот момент физические таблицы итогов обновляются.

Шаг 3. Проверка на минус. Мы выполняем запрос к остаткам именно по ЭТОМУ сотруднику. Если после нашего документа остаток стал отрицательным — значит, сотрудник пытается вернуть лишнее.


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

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

Важные замечания по оптимизации и надежности

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

Рассмотренный подход с использованием двух регистров является «золотым стандартом» в типовых конфигурациях 1С (таких как ERP или УТ 11). Он позволяет четко разделять ответственность: склад отвечает за наличие коробок на полках, а регистр эксплуатации — за персональную ответственность сотрудников.

← На главную