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