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