Разделы статей по 1С

Присоединяйтесь к разработчикам ERPSTAT

Присоединиться

10.10.2016

Оперативный учет. Задача №1

Автор: Белозерских Евгений

Первая задача раздела "Оперативный учет"

Для начала, давайте выделим основные моменты из условий задачи:
  • Используется 2 документа (Приходная накладная, Расходная накладная)
  • Компания продает И товары И услуги. И товар и услуга находятся в одной табличной части
  • Аналитика по складам не требуется
  • Необходим контроль количества при продаже товаров
  • Применяется партионный учет (ФИФО или ЛИФО)
  • Учетная политики действует в пределах ГОДА
  • Плюс необходимо реализовать два отчета (Продажи, Остатки товаров)

Готовый пример решения

Файл выгрузки (*.DT) с решением этой задачи можно скачать тут

Справочник "Номенклатура"

Т.к. компания продает товары, и может оказывать услуги, то для разделения товаров и услуг, добавим реквизит с наименование "Услуга" тип "Булево" (в рамках задачи, этого будет вполне достаточно)

Учетная политика

В задаче ПОДЧЕРКИВАЕТСЯ! Учетная политика действует в пределах года. Значит нам нужен регистр сведений, с периодичностью "В пределах года". Структура регистра довольно простая, это один ресурс "МетодСписания" с типом "ПеречислениеСсылка.УчетнаяПолитика"

Настройка учетной политики

Регистр накопления "ОстаткиНоменклатуры"

В задаче говорится, что возможен метод списания себестоимости ФИФО или ЛИФО, а значит эту себестоимость надо хранить. И так же себестоимость ведется в разрезе партий (т.е. документов "Приходная накладная"). Из выше сказанного, видно, что структуру регистра надо подправить:
  • Добавить ресурс "Стоимость"
  • Добавить измерение "Партия"
Структура регистра накопления "ОстаткиНоменклатуры"

Документ "Приходная накладная"

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

Конструктор движений Вот так выглядит результат работы конструктора движений
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
	// Данный фрагмент построен конструктором.
	// При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

	// регистр ОстаткиНоменклатуры Приход
	Движения.ОстаткиНоменклатуры.Записывать = Истина;
	Для Каждого ТекСтрокаСписокНоменклатуры Из СписокНоменклатуры Цикл
		Движение = Движения.ОстаткиНоменклатуры.Добавить();
		Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
		Движение.Период = Дата;
		Движение.Номенклатура = ТекСтрокаСписокНоменклатуры.Номенклатура;
		Движение.Количество = ТекСтрокаСписокНоменклатуры.Количество;
	КонецЦикла;

	//}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ
КонецПроцедуры
Но можно (так будет лучше, выше производительность) сделать по-другому. Получить все поля в запросе, и загрузить готовый результат в движения. Например вот так:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
	Движения.ОстаткиНоменклатуры.Записывать = Истина;

	ЗапросДанных	= Новый Запрос("ВЫБРАТЬ
	            	               |	_Товары.Номенклатура,
	            	               |	_Товары.Количество,
	            	               |	_Товары.Сумма КАК Стоимость,
	            	               |	_Товары.Ссылка КАК Партия,
	            	               |	_Товары.Ссылка.Дата КАК Период,
	            	               |	ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) КАК ВидДвижения
	            	               |ИЗ
	            	               |	Документ.ПриходнаяНакладная.СписокНоменклатуры КАК _Товары
	            	               |ГДЕ
	            	               |	_Товары.Ссылка = &Ссылка");
	ЗапросДанных.УстановитьПараметр("Ссылка", Ссылка);							   
	Движения.ОстаткиНоменклатуры.Загрузить(ЗапросДанных.Выполнить().Выгрузить());
КонецПроцедуры
И последний штрих! Будем заполнять реквизит "СуммаПоДокументу" перед записью. Для этого в модуле объекта используем процедуру "ПередЗаписью"
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
	СуммаПоДокументу	= СписокНоменклатуры.Итог("Сумма");
КонецПроцедуры
Движения документа "Приходная накладная"

Документ "Расходная накладная"

Теперь самое интересное! Читаем задачу 1.1, и отмечаем основные моменты которые необходимо учитывать при проведении документа "Расходная накладная":
  • Учитывать порядок списания себестоимости товаров, указанный в учетной политике (ФИФО или ЛИФО)
  • При проведении расходной накладной, необходимо контролировать остатки товаров
  • В случае нехватки товаров, выводить предупреждение, и не проводить документ
  • В табличной части могут быть как товары так И услуги, но нас интересуют только ТОВАРЫ и остатки по ним
Теперь давайте по-порядку! Открываем модуль объекта документа "Расходная накладная", и работаем с процедурой "ОбработкаПроведения".
Первое что определим, это порядок списания себестоимости!
ЗапросДанных	= Новый Запрос("ВЫБРАТЬ
	            	               |	ВЫБОР
	            	               |		КОГДА УчетнаяПолитика.МетодСписания = ЗНАЧЕНИЕ(Перечисление.УчетнаяПолитика.ЛИФО)
	            	               |			ТОГДА ""УБЫВ""
	            	               |		КОГДА УчетнаяПолитика.МетодСписания = ЗНАЧЕНИЕ(Перечисление.УчетнаяПолитика.ФИФО)
	            	               |			ТОГДА ""ВОЗР""
	            	               |	КОНЕЦ КАК МетодСписания
	            	               |ИЗ
	            	               |	РегистрСведений.УчетнаяПолитика.СрезПоследних(&Период, ) КАК УчетнаяПолитика");

	ЗапросДанных.УстановитьПараметр("Период",	Дата);

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

			Если КРаспределениюПоПартиям >= Обход.Остаток Тогда 
				Запись.Количество	= Обход.Остаток;
				Запись.Стоимость	= Обход.Себестоимость;
				
				КРаспределениюПоПартиям	= КРаспределениюПоПартиям - Обход.Остаток;
			Иначе
				Запись.Количество	= КРаспределениюПоПартиям;
				Запись.Стоимость	= Обход.Себестоимость / Обход.Остаток * КРаспределениюПоПартиям;
				
				КРаспределениюПоПартиям	= 0;
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;

Запустив конфигурацию в режиме "Предприятие", и создав документ "Расходная накладная" с необходимым количеством, можно убедиться что написанный алгоритм работает

Документ "Расходная накладная" задача 1.1
Движения документа "Расходная накладная" задача 1.1

Так же, рекомендуем посмотреть решение задачи 5.1 (Просмотр движений документа "Расходная накладная", для контроля правильности)