Решаем задачу для подготовки к экзамену "1С:Специалист" (1.12)

Чтобы получить от этой работы максимум пользы, старайтесь ответить на вопросы перед тем, как смотреть следующий шаг.

Вот фрагмент задачи 1.12 из сборника для подготовки к экзамену "1С:Специалист":

"Компания занимается оптовой торговлей. Принята следующая схема работы: поступление товаров отражается документом «Приходная накладная». По предварительной договоренности с покупателем менеджер может оформить резерв (документ «Резервирование товара»), причем наличие товара в этот момент не важно, товар может отсутствовать. Непосредственно отгрузка товара покупателю отражается документом «Расходная накладная», при этом происходит снятие резерва.

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

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

Приоритет устанавливается для каждого менеджера индивидуально и может меняться не чаще чем 1 раз в месяц. При продаже необходимо использовать приоритет менеджера, актуальный на дату продажи. Себестоимость товара рассчитывается как средняя по складу."

Недостаток этой задачи состоит в том, что не сформулировано чётко, как должен сниматься резерв. В контексте этой задачи товар можно разделить на четыре категории:

  1. Зарезервированный менеджерами с приоритетом, высшим ии равным приоритету продающего менеджера
  2. Зарезервированный менеджерами с приоритетом, меньшим приоритета продающего менеджера
  3. Зарезервированный самим менеджером
  4. Никем не зарезервированный
Товар из первой категории продавать нельзя. Что касается товара остальных категорий, то давайте добавим своё условие: "При продаже товара снятие резерва происходит так: Сначала снимается резерв продающего менеджера. Затем учитывается никем не зарезервированный товар (если он есть). И в последнюю очередь снимается резерв остальных менеджеров (то есть менеджеров с низшим приоритетом), причём сначала снимается резерв менеджеров с самым низким приоритетом."

Начнём решать. Сделаем регистр накопления "Остатки номенклатуры", измерения "Номенклатура", "Склад", ресурсы "Количество", "Сумма".

Регистр накопления "Резервы номенклатуры", измерения "Номенклатура", "Менеджер", ресурс "Количество".

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

Документ "Приходная накладная" имеет реквизит "Склад" и табличную часть "Остатки номенклатуры" и делает движения в регистр "Остатки номенклатуры". Обработку его проведения сделаем конструктором. Чтобы увидеть полный код обработки проведения документа "Приходная накладная", кликните здесь.



Также конструктором сделаем обработку проведения документа "Резервирование товара". Он будет делать движения в регистр "Резервы номенклатуры", и будет иметь реквизит "Менеджер" и табличную часть "Остатки номенклатуры" (Номенклатура, Количество). Чтобы увидеть полный код обработки проведения документа "Приходная накладная", кликните здесь.

Документ "Расходная накладная" имеет реквизиты "Склад" и "Менеджер", Табличную часть "Остатки номенклатуры" (Номенклатура, Количество).

Вся "соль" этой задачи в обработке проведения Расходной накладной. Что должна делать Расходная накладная? Списывать номенклатуру в регистре "Остатки номенклатуры" и, при необходимости, в регистре "Резервы номенклатуры". Какая информация нам для этого потребуется?

Давайте начнём делать обработку проведения. Что напишем сначала?

Запишем пустые наборы записей в регистры, на случай, если там имеются записи этого документа. Затем сделаем запрос к табличной части документа и сохраним её данные во временной таблице. Выберем из неё номенклатуру и заблокируем регистры по значениям этой номенклатуры.



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

ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = РезультатЗапроса;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");

Блокировка.Заблокировать();
 

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


	Запрос.Текст = 
		"ВЫБРАТЬ ПЕРВЫЕ 1
		|	ПриоритетыМенеджеровСрезПоследних.Приоритет КАК Приоритет
		|ПОМЕСТИТЬ ВТПриоритетЭтогоМенеджера
		|ИЗ
		|	РегистрСведений.ПриоритетыМенеджеров.СрезПоследних(&МоментВремени, Менеджер = &Менеджер) КАК ПриоритетыМенеджеровСрезПоследних
		|
		|ИНДЕКСИРОВАТЬ ПО
		|	Приоритет
		|;
		|
		|////////////////////////////////////////////////////////////////////////////////
		|ВЫБРАТЬ
		|	ПриоритетыМенеджеровСрезПоследних.Менеджер КАК Менеджер,
		|	ПриоритетыМенеджеровСрезПоследних.Приоритет КАК Приоритет
		|ПОМЕСТИТЬ МенеджерыНизкогоПриоритета
		|ИЗ
		|	ВТПриоритетЭтогоМенеджера КАК ВТПриоритетЭтогоМенеджера
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПриоритетыМенеджеров.СрезПоследних(&МоментВремени, ) КАК ПриоритетыМенеджеровСрезПоследних
		|		ПО ВТПриоритетЭтогоМенеджера.Приоритет > ПриоритетыМенеджеровСрезПоследних.Приоритет
		|
		|ИНДЕКСИРОВАТЬ ПО
		|	Менеджер
		|;
		|

 
 

Ранее мы сделали временную таблицу "ВТсписокНоменклатуры". Давайте сделаем на основе этой таблицы другую временную таблицу (назовём её "ВТВсёПоНоменклатуре"), в которой соберём информацию, связанную с номенклатурой

Пусть это будет третий запрос пакета.


|ВЫБРАТЬ
		|	ВТСписокНоменклатуры.Номенклатура КАК Номенклатура,
		|	ВТСписокНоменклатуры.Количество КАК КоличествоПоДокументу,
		|	ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) - ЕСТЬNULL(РезервыНоменклатурыОстатки.КоличествоОстаток, 0) КАК НезарезервированноеКоличество,
		|	ЕСТЬNULL(РезервыНоменклатурыОстатки1.КоличествоОстаток, 0) КАК КоличествоЗарезервированноеЭтимМенеджером,
		|	ЕСТЬNULL(ОстаткиНоменклатурыОстатки1.КоличествоОстаток, 0) КАК КоличествоНаСкладе,
		|	ЕСТЬNULL(ОстаткиНоменклатурыОстатки1.СуммаОстаток, 0) КАК СуммаНаСкладе
		|ПОМЕСТИТЬ ВТВсёПоНоменклатуре
		|ИЗ
		|	ВТСписокНоменклатуры КАК ВТСписокНоменклатуры
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
		|				&МоментВремени,
		|				Номенклатура В
		|					(ВЫБРАТЬ
		|						ВТСписокНоменклатуры.Номенклатура
		|					ИЗ
		|						ВТСписокНоменклатуры)) КАК ОстаткиНоменклатурыОстатки
		|		ПО ВТСписокНоменклатуры.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РезервыНоменклатуры.Остатки(
		|				&МоментВремени,
		|				Номенклатура В
		|					(ВЫБРАТЬ
		|						ВТСписокНоменклатуры.Номенклатура
		|					ИЗ
		|						ВТСписокНоменклатуры)) КАК РезервыНоменклатурыОстатки
		|		ПО ВТСписокНоменклатуры.Номенклатура = РезервыНоменклатурыОстатки.Номенклатура
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РезервыНоменклатуры.Остатки(
		|				&МоментВремени,
		|				Номенклатура В
		|						(ВЫБРАТЬ
		|							ВТСписокНоменклатуры.Номенклатура
		|						ИЗ
		|							ВТСписокНоменклатуры)
		|					И Менеджер = &Менеджер) КАК РезервыНоменклатурыОстатки1
		|		ПО ВТСписокНоменклатуры.Номенклатура = РезервыНоменклатурыОстатки1.Номенклатура
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
		|				&МоментВремени,
		|				Номенклатура В
		|						(ВЫБРАТЬ
		|							ВТСписокНоменклатуры.Номенклатура
		|						ИЗ
		|							ВТСписокНоменклатуры)
		|					И Склад = &Склад) КАК ОстаткиНоменклатурыОстатки1
		|		ПО ВТСписокНоменклатуры.Номенклатура = ОстаткиНоменклатурыОстатки1.Номенклатура
		|
		|ИНДЕКСИРОВАТЬ ПО
		|	Номенклатура
		|;
	

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

Предлагаю также добавить в имеющуюся временную таблицу "ВТМенеджерыНизкогоПриоритета" данные по резервам по каждому менеджеру из этой таблицы каждой номенклатуры из документа, и создать на её основе другую временную таблицу "ВТРезервыМенеджеров".


|ВЫБРАТЬ
		|	ВТМенеджерыНизкогоПриоритета.Менеджер КАК Менеджер,
		|	ВТМенеджерыНизкогоПриоритета.Приоритет КАК Приоритет,
		|	РезервыНоменклатурыОстатки.Номенклатура КАК Номенклатура,
		|	ЕСТЬNULL(РезервыНоменклатурыОстатки.КоличествоОстаток, 0) КАК РезервМенеджераПоНоменклатуре
		|ПОМЕСТИТЬ ВТРезервыМенеджеров
		|ИЗ
		|	ВТМенеджерыНизкогоПриоритета КАК ВТМенеджерыНизкогоПриоритета
		|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.РезервыНоменклатуры.Остатки(
		|				&МоментВремени,
		|				Менеджер В
		|						(ВЫБРАТЬ
		|							ВТМенеджерыНизкогоПриоритета.Менеджер КАК Менеджер
		|						ИЗ
		|							ВТМенеджерыНизкогоПриоритета КАК ВТМенеджерыНизкогоПриоритета)
		|					И Номенклатура В
		|						(ВЫБРАТЬ
		|							ВТВсёПоНоменклатуре.Номенклатура КАК Номенклатура
		|						ИЗ
		|							ВТВсёПоНоменклатуре КАК ВТВсёПоНоменклатуре)) КАК РезервыНоменклатурыОстатки
		|		ПО ВТМенеджерыНизкогоПриоритета.Менеджер = РезервыНоменклатурыОстатки.Менеджер
		|;

Теперь мы можем объединить временные таблицы "ВТВсёПоНоменклатуре" и "ВТРезервыМенеджеров" в финальном запросе пакета, который выберет всё, что нам надо. Какое соединение используем?

Используем левое соединение по номенклатуре, слева будет "ВТВсёПоНоменклатуре", поскольку нам надо будет обходить всю номенклатуру. В таблице же "ВТРезервыМенеджеров" может быть несколько значений для той или иной номенклатуры (то есть несколько менеджеров, имеющих по ней резервы), а может не быть ни одного. Из этого следует важный вывод: придётся прописать в запросе итоги, чтобы в итоговой выборке обходить номенклатуру, в в выборке детальных записей обходить менеджеров.

Как пропишем итоги? Как произведём упорядочивание?


	|ВЫБРАТЬ
		|	ВТВсёПоНоменклатуре.Номенклатура КАК Номенклатура,
		|	ВТВсёПоНоменклатуре.КоличествоПоДокументу КАК КоличествоПоДокументу,
		|	ВТВсёПоНоменклатуре.НезарезервированноеКоличество КАК НезарезервированноеКоличество,
		|	ВТВсёПоНоменклатуре.КоличествоЗарезервированноеЭтимМенеджером КАК КоличествоЗарезервированноеЭтимМенеджером,
		|	ВТВсёПоНоменклатуре.КоличествоНаСкладе КАК КоличествоНаСкладе,
		|	ВТВсёПоНоменклатуре.СуммаНаСкладе КАК СуммаНаСкладе,
		|	ВТРезервыМенеджеров.Менеджер КАК Менеджер,
		|	ВТРезервыМенеджеров.Приоритет КАК Приоритет,
		|	ЕСТЬNULL(ВТРезервыМенеджеров.РезервМенеджераПоНоменклатуре, 0) КАК РезервМенеджераПоНоменклатуре
		|ИЗ
		|	ВТВсёПоНоменклатуре КАК ВТВсёПоНоменклатуре
		|		ЛЕВОЕ СОЕДИНЕНИЕ ВТРезервыМенеджеров КАК ВТРезервыМенеджеров
		|		ПО ВТВсёПоНоменклатуре.Номенклатура = ВТРезервыМенеджеров.Номенклатура
		|
		|УПОРЯДОЧИТЬ ПО
		|	Номенклатура,
		|	Приоритет
		|ИТОГИ
		|	МАКСИМУМ(КоличествоПоДокументу),
		|	МАКСИМУМ(НезарезервированноеКоличество),
		|	МАКСИМУМ(КоличествоЗарезервированноеЭтимМенеджером),
		|	МАКСИМУМ(КоличествоНаСкладе),
		|	МАКСИМУМ(СуммаНаСкладе)
		|ПО
		|	Номенклатура";

В итогах я прописал агрегатную функцию МАКСИМУМ, хотя думаю, подошли бы и функции МИНИМУМ, СРЕДНЕЕ. Для нас важно, чтобы в итогах не изменились те значения, которые были в основном теле запроса. Упорядочивание я произвёл сначала по номенклатуре, хотя само по себе это упорядочивание для нас не важно. Важно упорядочивание по приоритету, так как сперва должны списываться резервы с низшим приоритетом. РезервМенеджераПоНоменклатуре я обернул в функцию ЕСТЬNULL, так как резервов менеджеров может не быть, и тогда при обходе выборки будет некорректно работать сравнение.

Всё, с запросом закончили. Пора делать выборку. Как спишем остатки номенклатуры? Какую проверку проведём?


	РезультатЗапроса = Запрос.Выполнить();
	
	ВыборкаИтог = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
	
	Пока ВыборкаИтог.Следующий() Цикл
		
		Если 			
		ВыборкаИтог.КоличествоПоДокументу > ВыборкаИтог.КоличествонаСкладе
		Тогда
		Отказ = ИСТИНА;
		Нехватка = ВыборкаИтог.КоличествоПоДокументу - ВыборкаИтог.КоличествонаСкладе;
		Сообщение = Новый СообщениеПользователю;
		Сообщение.Текст = "Не хватает на складе номенклатуры "+ ВыборкаИтог.Номенклатура + "в количестве " + Нехватка;
			Сообщение.Сообщить(); 		
		КонецЕсли; 
		
		Если Отказ Тогда
		
			Продолжить;
		
		КонецЕсли; 
		
		Движение = Движения.ОстаткиНоменклатуры.ДобавитьРасход();
		Движение.Период = Дата;
		Движение.Склад = Склад;
		Движение.Номенклатура = ВыборкаИтог.Номенклатура;
		Движение.Количество = ВыборкаИтог.КоличествоПоДокументу;
		
		Если 
			ВыборкаИтог.КоличествоПоДокументу = ВыборкаИтог.КоличествоНаСкладе
			Тогда
		Движение.Сумма = ВыборкаИтог.СуммаНаСкладе
			Иначе
		Движение.Сумма = ВыборкаИтог.СуммаНаСкладе*ВыборкаИтог.КоличествоПоДокументу/ВыборкаИтог.КоличествоНаСкладе;
		КонецЕсли; 


Списание остатков произвёл "классическим" способом, с проверкой остатков на складе, с полным списанием суммы, если количество в документе равно количеству остатка.

Теперь пора списывать резервы. Что следует сделать в первую очередь? Как учесть тот факт, что любого вида резервов, которые мы будем последовательно списывать, может и не быть?

Думаю, что прежде всего надо создать переменную, в которую записать количество, которое надо списать из резервов, и значение которой по мере списания резервов будет уменьшаться. А перед списанием следующего вида резервов будем выполнять проверки, что этот вид резерва имеется, и что количество, которое надо списать из резервов больше нуля.


		
		НадоСписатьРезервов = ВыборкаИтог.КоличествоПоДокументу;
		
		Если ВыборкаИтог.КоличествоЗарезервированноеЭтимМенеджером>0 Тогда
		
		Движение = Движения.РезервыНоменклатуры.ДобавитьРасход();
		Движение.Период = Дата;
		Движение.Менеджер = Менеджер;
		Движение.Номенклатура = ВыборкаИтог.Номенклатура;				
		Движение.Количество = Мин(ВыборкаИтог.КоличествоЗарезервированноеЭтимМенеджером, НадоСписатьРезервов);
		
		НадоСписатьРезервов = НадоСписатьРезервов- Движение.Количество;
	
		КонецЕсли; 
			
		
		Если НадоСписатьРезервов > 0 
			И ВыборкаИтог.НеЗарезервированноеКоличество>0
			Тогда		
		
		Движение = Движения.РезервыНоменклатуры.ДобавитьРасход();
		Движение.Период = Дата;
		Движение.Менеджер = Менеджер;
		Движение.Номенклатура = ВыборкаИтог.Номенклатура;
		Движение.Количество = Мин(ВыборкаИтог.НеЗарезервированноеКоличество, НадоСписатьРезервов);
		
		НадоСписатьРезервов = НадоСписатьРезервов - Движение.Количество;

	    КонецЕсли; 
		
		Если НадоСписатьРезервов > 0 Тогда
		
			ВыборкаДетальныезаписи = ВыборкаИтог.Выбрать();

				Пока ВыборкаДетальныезаписи.Следующий() И НадоСписатьРезервов > 0 Цикл
				
				
		Движение = Движения.РезервыНоменклатуры.ДобавитьРасход();
		Движение.Период = Дата;
		Движение.Менеджер = ВыборкаДетальныезаписи.Менеджер;
		Движение.Номенклатура = ВыборкаДетальныеЗаписи.Номенклатура;
		
		Движение.Количество = Мин(ВыборкаДетальныеЗаписи.РезервМенеджераПоНоменклатуре, НадоСписатьРезервов);
		
		НадоСписатьРезервов = НадоСписатьРезервов - Движение.Количество;		
				
			КонецЦикла; 
				
		КонецЕсли;

Однако после прохождения цикла по детальным записям может оказаться, что переменная "НадоСписатьРезервов" всё равно больше нуля. Что будем делать тогда?

Если переменная "НадоСписатьРезервов" после всех списаний больше нуля, то резервов не хватает, и придётся отказаться от проведения. Также надо сообщить об этом пользователю. Где напишем отказ?

Отказ и сообщение напишем после окончания цикла по детальным записям.


	
		НадоСписатьРезервов = НадоСписатьРезервов - Движение.Количество;		
				
			КонецЦикла; 
				
		КонецЕсли;
		
			Если НадоСписатьРезервов > 0 Тогда
		
			Отказ = Истина;
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = "Не хватает резерва  "+ ВыборкаИтог.Номенклатура+" в количестве "+ НадоСписатьРезервов;
			Сообщение.Сообщить(); 
		
		КонецЕсли; 

	КонецЦикла;
КонецПроцедуры


Чтобы увидеть полный код обработки проведения документа "Расходная накладная", кликните здесь.
Конфигурацию решения задачи можно скачать здесь.
Об ошибках сообщайте по электронной почте obuchmat@mail.ru
Вот ещё одна задача

Наверх

На домашнюю страницу