1C
January 30

Задача 3

Условие данной задачи во многом повторяет условие задачи 1. Отличие заключается в том, что учетная политика (ФИФО/ЛИФО) не ведется, а просто в табличной части документа РасходнаяНакладная для каждого товара пользователь указывает партию, которую необходимо списать. В том случае, если товара по указанной партии не хватает, то документ не проводится и выводится соответствующее сообщение о нехватке.

Необходимо построить отчет по анализу продаж товаров за период и остаткам товара на указанную дату. Ориентировочный вид обоих отчетов приведен ниже:

Где:
Интервал = «Дата первой отгрузки» - «Дата последней отгрузки» / «Количество отгрузок» (или "разовая", если «Количество отгрузок» = 1)
Срок = «Конец периода отчета» - «Дата последней отгрузки»


Очевидно, что мы имеем дело еще с одной небольшой вариацией задачи 1, решение которой возьмем за основу. Т.к. учетная политика здесь отсутствует как таковая, то первым делом выкинем из конфигурации одноименный регистр сведений. А также уберем из обработки проведения документа РасходнаяНакладная обращение к этому регистру и сортировку партий в запросе.

В задаче требуется списывать не все доступные партии, а только те, что указаны в табличной части документа РасходнаяНакладная, поэтому добавим в его табличную часть СписокНоменклатуры новый реквизит Партия типа ДокументСсылка.ПриходнаяНакладная. Т.к. по условию задачи услуги и товары указываются в одной табличной части, то свойство Проверка заполнения нового реквизита оставим имеющим значение Не проверять. Подправим текст запроса таким образом, чтобы он возвращал остатки сразу для пары {номенклатура, партия} (новые строки выделены 4мя вертикальными полосками ||||):

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

    Запрос.УстановитьПараметр("Ссылка", Ссылка);
    Запрос.УстановитьПараметр("МоментВремени", МоментВремени());

Заметим, что в запросе отсутствует упорядочивание. Дело в том, что для каждой пары {номенклатура, партия} из табличной части мы выбираем из регистра не список партий (как в задачах 1 или 2), а только одну партию.

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

Но при этом мы, конечно же, не подсчитываем суммарный остаток по всем партиям, указанным для данной номенклатуры, поскольку необходимо контролировать наличие остатков достаточных для отгрузки, по каждой партии в отдельности.

Одним из важнейших аспектов программирования является правильная блокировка данных. Здесь мы продемонстрируем использование управляемой блокировки. В регистре накопления ОстаткиНоменклатуры блокируются все указанные в табличной части документа СписокНоменклатуры сочетания номенклатуры и партии. То есть другой документ сможет списывать другую номенклатуру с той же партии и ту же номенклатуру с другой партии. Таким образом, мы блокируем только то, что нам понадобится, И позволяем другим документам беспрепятственно работать со всем остальным.

Перед тем как выполнить чтение данных из регистра ОстаткиНоменклатуры, мы стираем прежние движения этого документа, если они были, записывая пустой набор движений. Но ещё прежде мы указываем, что все остатки и обороты, на которые влияли наши прежние движения, следует БлокироватьДляИзменения. Это на тот случай, если по итогам проверки остатков в партиях, мы решимся отказаться от записи новых движений и вернуть прежние. Ведь пока мы производим проверки и формируем движения, другой документ может забрать то, что временно освободилось, когда мы стерли прежние движения.

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

Ну, вот теперь мы можем смело читать остатки и приступать к формированию движений. В принципе это делается также, как и в задаче 1, только проверка наличия остатков погружается в цикл обхода детальных записей, не требуется контролировать сколько осталось списать, а сам этот цикл выполняется только когда номенклатура не является услугой.

    Результат = Запрос.Выполнить();
    ВыборкаНоменклатура = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

    Пока ВыборкаНоменклатура.Следующий() Цикл

        ТекСебестоимость = 0;

        Если НЕ ВыборкаНоменклатура.ЭтоУслуга Тогда 

        ВыборкаДетальныеЗаписи = ВыборкаНоменклатура.Выбрать();

            Пока ВыборкаДетальныеЗаписи.Следующий() Цикл

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

                    Отказ = Истина;
                КонецЕсли;

                Если Отказ Тогда
                    Продолжить;
                КонецЕсли;

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

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

Отчет «Остатки товаров» полностью совпадает с одноименным отчетом задачи 1, поэтому его реализация рассматриваться здесь не будет. Создадим новый отчет АнализПродаж, добавим его в подсистему Оперативный учет, а затем на вкладке «Основные» нажмем кнопку «Открыть схему компоновки данных». В схему добавим набор данных вида «Запрос». Укажем следующий текст запроса:

ВЫБРАТЬ
    ПродажиОбороты.Номенклатура,
    МИНИМУМ(ПродажиОбороты.Период) КАК Минимум,
    МАКСИМУМ(ПродажиОбороты.Период) КАК Максимум,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ПродажиОбороты.Регистратор) КАК N,
    СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество,
    СУММА(ПродажиОбороты.ВыручкаОборот) КАК Продажа,
    СУММА(ПродажиОбороты.СебестоимостьОборот) КАК Себестоимость
ИЗ
    РегистрНакопления.Продажи.Обороты(, , Регистратор, ) КАК ПродажиОбороты

СГРУППИРОВАТЬ ПО
    ПродажиОбороты.Номенклатура

Запрос вернет основные данные для нашего отчета, но часть необходимой нам информации - поля Прибыль/Интервал/Срок - среди них отсутствуют. Ничто не мешает усложнить запрос таким образом, чтобы он возвращал все, что нам нужно. Но мы предпочитаем действовать иначе, а именно - собираемся вычислять значения этих полей в самой схеме компоновки данных. Для этого перейдем на вкладку «Вычисляемые поля» и внесем на ней ряд изменении:

Закладка "Вычисляемые поля" отчета АнализПродаж

Перейдем на закладку «Параметры». Установим параметры отчета согласно рисунка.

Закладка "Параметры" отчета АнализПродаж

Перейдем на закладку «Настройки». Добавим в структуру нашего отчета новую группировку (клавиша Ins), но поле группировки указывать не будем (клавиша Enter). На вкладке «Выбранные поля» укажем список полей отчета, согласно требуемой формы.

Закладная "Выбранные поля" настроек отчета АнализПродаж

Рядом с вкладкой «Выбранные поля» расположена вкладка «Параметры». Перейдем на нее и выполним настройку параметра «Период» отчета.

Закладная "Параметры" настроек отчета АнализПродаж

Последний штрих - переходим на вкладку «Другие настройки» и задаем заголовок отчета.