Во-вторых, руководство компании решило наконец-то завершить
«эксперименты» по ручному вводу стоимости расходуемых материалов и перейти на автоматический расчет стоимости расходуемых материалов «по среднему».
В-третьих, при проведении документа ОказаниеУслуги необходимо контролировать остатки расходуемых товаров на складе. Если товаров не хватает, выдавать предупреждение и не проводить документ.
Поэтому изменения, вносимые нами в документ ОказаниеУслуги, будут преследовать три цели:
- повышение скорости выполнения процедуры;
- автоматическое определение стоимости расходуемых материалов при проведении документа;
- разделение алгоритма проведения документа на оперативный и неоперативный режимы и контроль остатков в случае оперативного проведения документа.
Теория: особенности использования ссылочных данных
Термином «ссылочные данные» мы будем обозначать данные, хранящиеся в базе данных, доступ к которым возможен при помощи объектов встроенного языка вида Ссылка: СправочникСсылка.<имя>, ДокументСсылка.<имя> и т.д. Для того чтобы дальнейшее изложение было понятнее, мы построим объяснение на примере получения ссылки на вид номенклатуры при проведении документа ОказаниеУслуги.Не все данные, хранящиеся в базе данных, являются ссылочными. Это связано с тем, что в модели данных «1С:Предприятия» существует деление на данные, представляющие объектные сущности (справочники, планы счетов, документы и т.д.), и данные, представляющие необъектные сущности (регистры сведений, регистры накопления и т.д.).
С точки зрения платформы некоторая совокупность объектных данных определяется не только значениями своих полей, но и самим фактом своего существования. Другими словами, удалив из базы некоторую совокупность объектных данных, мы не сможем вернуть систему в то же состояние, которое было до удаления. Даже если мы заново создадим ту же самую совокупность объектных данных с теми же самыми значениями полей, с точки зрения системы это будет ДРУГАЯ совокупность объектных данных.
Каждую такую совокупность объектных данных, уникальную с точки зрения системы, называют объектом базы данных.
Для того чтобы система могла отличить один объект базы данных от другого, каждый объект базы данных (совокупность объектных данных) имеет внутренний идентификатор. Различные объекты базы данных всегда будут иметь разные внутренние идентификаторы. Этот идентификатор хранится вместе с остальными данными объекта в специальном поле Ссылка. Необъектные данные хранятся в виде записей и с точки зрения системы определяются исключительно значениями своих полей. Таким образом, удалив некоторую запись и записав после этого новую, с точно такими же значениями всех полей, мы получим то же самое состояние базы данных, которое было до удаления.
Таким образом, поскольку мы можем однозначно указать на каждый объект базы данных, у нас появляется возможность хранить такой указатель в полях других таблиц базы данных, выбирать его в поле ввода, указывать в параметрах запроса при поиске по ссылке и т.д. Во всех этих случаях как раз и будет использоваться объект встроенного языка вида Ссылка. Фактически
этот объект хранит только внутренний идентификатор, находящийся в поле Ссылка.
Например, если взять наш документ ОказаниеУслуги, то в поле, хранящем реквизит табличной части Номенклатура, на самом деле находится внутренний идентификатор, указывающий на элемент справочника Номенклатура.

Когда в обработчике события ОбработкаПроведения документа ОказаниеУслуги мы присваиваем значение реквизита табличной части Номенклатура какой-либо переменной, мы имеем дело с объектом встроенного языка ДокументОбъект.ОказаниеУслуги. Этот объект содержит в себе значения всех реквизитов документа и реквизитов его табличных частей. Поэтому обращение [Движение.Материал = ТекСтрокаПереченьНоменклатуры.Номенклатура;] приводит к тому, что мы просто читаем данные, хранящиеся в оперативной памяти, в этом самом объекте встроенного языка.

Однако когда мы обращаемся к виду номенклатуры как к реквизиту того элемента справочника, ссылка на который указана в табличной части документа [Если ТекСтрокаПереченьНоменклатуры.Номенклатура. ВидНоменклатуры = Перечисления.ВидыНоменклатуры.Материал Тогда], происходит буквально следующее

Поскольку в объекте ДокументОбъект.ОказаниеУслуги есть только ссылка на элемент справочника Номенклатура и больше никаких данных об этом элементе нет, платформа возьмет эту ссылку и обратится по ней в кеш
объектов в надежде найти там данные того объекта, ссылка на который у нее есть.
Если кеш объектов не будет иметь нужных данных, он обратится к базе данных с тем, чтобы прочитать все данные объекта, ссылкой на который он обладает.
После того как все данные, хранящиеся в реквизитах нужного элемента справочника и в реквизитах его табличных частей, будут считаны в кеш объектов, кеш объектов вернет запрашиваемую ссылку, хранящуюся в реквизите ВидНоменклатуры справочника Номенклатура.
Как несложно догадаться, подобное обращение к базе данных требует большего количества времени, нежели просто чтение из оперативной памяти. При интерактивном заполнении документа подобные задержки ничтожно малы, по сравнению со скоростью работы пользователя. Однако при выполнении большого количества расчетов (например, при проведении больших документов, содержащих несколько тысяч строк) разница во времени может быть довольно заметной.
Из всего вышесказанного можно сделать следующий вывод: если алгоритм проведения документа использует только те данные, которые присутствуют в реквизитах документа (и его табличных частей), вполне достаточно использовать конструктор движений документа (как это было у нас в случае с документом ПриходнаяНакладная).
Если же в алгоритме проведения требуется анализировать дополнительные реквизиты объектов, ссылки на которые содержатся в документе, а также использовать результаты расчета итогов регистров, следует использовать запросы для более быстрой выборки данных из базы данных.
То же самое справедливо в отношении выполнения любых участков программы, критичных по производительности. Механизм запросов лучше
«читает» информационную базу и может за один раз выбрать только те данные, которые необходимы. Поэтому, например, в типовых решениях вы
практически не увидите использования объекта встроенного языка СправочникВыборка.<имя>. Вместо этого повсеместно используются запросы к базе данных.
Повышение скорости проведения
Избавимся от «вредной» конструкции ТекСтрокаПереченьНоменклатуры.Номенклатура.ВидНоменклатуры.Откроем модуль документа ОказаниеУслуги. Напомним, как выглядит сейчас процедура проведения этого документа:

Другими словами, все данные, необходимые для проведения документа, мы получаем из самого документа, и только для определения
того, чем является номенклатура (товаром или услугой), мы обращаемся к базе данных, читая данные всего объекта Номенклатура [Если ТекСтрокаПереченьНоменклатуры.Номенклатура.ВидНоменклатуры].
Забегая вперед, скажем, что это не единственные данные, которые не содержатся в самом документе и которые в то же время будут нужны нам для правильного его проведения.
Поэтому поступим следующим образом: все данные, связанные с номенклатурой, которая содержится в табличной части документа, мы будем получать с помощью запроса к базе данных. А данные, связанные с самим документом (например, дата документа, склад), мы по-прежнему будем получать из документа. Такой подход позволит нам читать только нужные данные и за счет этого максимально ускорить проведение документа.
Итак, запросом мы будем получать:
- номенклатуру,
- количество,
- сумму,
- стоимость.
- дата,
- клиент,
- мастер,
- склад.

Подтвердим, что мы хотим создать новый запрос. В окне конструктора запросов перейдем на закладку Таблицы и поля и выберем таблицу ОказаниеУслугиПереченьНоменклатуры – это табличная часть документа ОказаниеУслуги.
Из этой таблицы нам нужны поля – Номенклатура, НоменклатураВидНоменклатуры, Количество, Сумма и Стоимость.

Но нам нужны не все записи этой таблицы, а только те, которые относятся к нашему документу. Поэтому перейдем на закладку Условия и зададим условие отбора из таблицы документа только строк проводимого документа. Для этого перетащим поле Ссылка в список условий запроса [ОказаниеУслугиПереченьНоменклатуры.Ссылка = &Ссылка]. Ссылка на этот документ будет передана в параметр запроса Ссылка.

Также следует учесть, что в табличной части документа одна и та же номенклатура может встречаться несколько раз.
Поэтому на закладке Группировка сгруппируем наши записи по полям Номенклатура и НоменклатураВидНоменклатуры, а рассчитывать будем сумму значений для полей Количество и Сумма.
Благодаря этому в результате значения номенклатуры повторяться не будут, и для каждого из них будут посчитаны суммарные значения по полям Количество и Сумма, если в табличной части документа содержится несколько строк с одинаковой номенклатурой.
Также в состав суммируемых полей включим и поле Стоимость. По нему будем рассчитывать, например, функцию Максимум.
Мы подразумеваем, что для разных строк одной и той же номенклатуры стоимость будет одинаковой, поэтому функция Максимум нужна нам лишь для того, чтобы получить одно из имеющихся значений стоимости.

На закладке Объединения/Псевдонимы зададим псевдонимы для полей Количество и Сумма – КоличествоВДокументе и СуммаВДокументе, а для поля НоменклатураВидНоменклатуры зададим псевдоним ВидНоменклатуры просто для облегчения чтения запроса.

Нажмем ОК и посмотрим, какой текст запроса сформировал конструктор.
Комментарии конструктора запроса в начале и конце фрагмента можно удалить.
Поскольку для построения запроса мы использовали Конструктор запроса с обработкой результата, конструктор написал за нас код для выполнения и обхода записей запроса. Прокомментируем этот код.
Как вы уже знаете, для работы с запросами используется объект встроенного языка Запрос. Вначале создается новый объект Запрос и помещается в переменную Запрос. Затем в свойство Текст объекта Запрос помещается сам текст запроса (Запрос.Текст = …).
После этого устанавливается значение параметра запроса &Ссылка как ссылка на тот документ, в модуле которого мы сейчас находимся [Запрос.УстановитьПараметр("Ссылка", Ссылка);].
Затем запрос выполняется (Запрос.Выполнить()), получается объект РезультатЗапроса, и выполняется его метод Выбрать(), который формирует выборку записей из результата запроса.
Таким образом, получается объект ВыборкаИзРезультатаЗапроса, который помещается в переменную ВыборкаДетальныеЗаписи.
Далее, используя метод этого объекта Следующий() (ВыборкаДетальныеЗаписи.Следующий()), мы будем в цикле обходить выборку записей запроса.
Выполняя метод выборки запроса ВыборкаДетальныеЗаписи.Следующий(), мы на каждом шаге цикла позиционируем указатель на следующую запись выборки, пока не будет достигнут конец выборки.
Чтобы в цикле получить значение какого-либо поля выборки из результата запроса, мы будем обращаться к полям запроса через точку от переменной ВыборкаДетальныеЗаписи, которая содержит текущую строку выборки запроса. Например, так: ВыборкаДетальныеЗаписи.Номенклатура.
Теперь нам осталось перенести существовавшие ранее в этом модуле строки, описывающие движения регистров, внутрь цикла обхода результата запроса.
[Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
// Вставить обработку выборки ВыборкаДетальныеЗаписи КонецЦикла;]
Сначала вместо комментария «// Вставить обработку выборки ВыборкаДетальныеЗаписи» перенесем условие проверки и весь код, формирующий движения по регистрам ОстаткиМатериалов и СтоимостьМатериалов.

В условии заменим ТекСтрокаПереченьНоменклатуры.Номенклатура на ВыборкаДетальныеЗаписи, так как вид номенклатуры мы теперь получаем из запроса. В движениях также заменим ТекСтрокаПереченьНоменклатуры на ВыборкаДетальныеЗаписи.

Не забудем, что для поля Количество мы задали псевдоним в запросе, поэтому заменим его на КоличествоВДокументе.

Теперь перенесем формирование движений по регистру Продажи.

Здесь произведем аналогичные замены.
ТекСтрокаПереченьНоменклатуры заменим на ВыборкаДетальныеЗаписи.

Поля запроса Сумма и Количество заменим на СуммаВДокументе и КоличествоВДокументе.

Оставшийся цикл обхода табличной части можно удалить

В результате процедура проведения примет следующий вид


В режиме «1С:Предприятие» Теперь нужно запустить
«1С:Предприятие» в режиме отладки, перепровести документы Оказание услуги и проверить, что ничего не изменилось. Таким образом, мы выполнили первый пункт нашего «плана» – избавились в процедуре проведения от считывания всех данных объекта Номенклатура и тем самым оптимизировали выполнение процедуры проведения.
Автоматический расчет стоимости
Теперь приступим ко второму этапу нашего плана.До сих пор стоимость расходуемых материалов мы вписывали в документ Оказание услуги вручную, при его создании.
Теперь же будем определять стоимость номенклатуры по среднему: для каждой номенклатуры делить ее общую, суммарную стоимость на то количество этой номенклатуры, которое у нас имеется, и таким образом получать среднюю стоимость одной единицы этой номенклатуры.
Чтобы выполнить такой расчет, нам понадобятся дополнительные данные, которых у нас сейчас нет.
Для каждой номенклатуры из табличной части нам понадобятся:
- ее стоимость, хранящаяся в регистре СтоимостьМатериалов;
- общее ее количество на всех складах, хранящееся в регистре ОстаткиМатериалов.
Поэтому нам нужно будет доработать наш запрос таким образом, чтобы он получал из базы данных и эти данные тоже. Таким образом, нам хотелось бы, чтобы запрос возвращал следующие поля для каждой номенклатуры, которая есть в документе

Первые четыре поля мы можем получить (и уже получаем) из табличной части самого документа, а вот последние два нужно будет получить из других таблиц базы данных:
- стоимость – из регистра СтоимостьМатериалов;
- остатки на всех складах – из регистра ОстаткиМатериалов.

Это значит, что наш запрос должен содержать два левых соединения таблицы документа с другими таблицами: одно – с таблицей РегистрНакопления.СтоимостьМатериалов.Остатки, другое – с таблицей РегистрНакопления.ОстаткиМатериалов.Остатки.
Казалось бы, все готово для того, чтобы составить запрос. Но обратите внимание на важную деталь: в предложенной схеме виртуальные таблицы будут возвращать стоимость и остатки номенклатуры абсолютно для всей номенклатуры. А нас интересует только та номенклатура, которая указана в нашем документе.
На маленькой базе эта особенность может почти не проявляться – количество различных элементов номенклатуры в справочнике сравнимо с количеством различных элементов номенклатуры в документе.
Но представьте реальную базу. В справочнике Номенклатура – 15000 наименований, например. А в документе – всего 5 наименований. Получится, что сначала виртуальная таблица остатков будет усиленно трудиться и рассчитывать нам стоимость (или остатки) по всем 15 000 наименованиям номенклатуры, а в результате, когда мы левым соединением станем соединять ее с таблицей документа, мы из этих 15000 строк стоимости (или остатков) возьмем всего лишь 5 строк – для той номенклатуры, которая указана в документе. Остальные 14995 строк будут просто отброшены – система напрасно их рассчитывала.
Естественно, такая расточительность непозволительна в реальных условиях, и такой запрос является очень неоптимальным. Поэтому во все виртуальные таблицы, которые мы будем использовать, нужно добавить условие отбора только той номенклатуры, которая содержится в табличной части нашего документа. В этом случае стоимость и остатки будут рассчитаны не для всей номенклатуры вообще, а только для нужной нам номенклатуры. В результате схема нашего запроса будет выглядеть следующим образом.

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

В режиме «Конфигуратор»
Первое, что мы сделаем, – удалим реквизит табличной части Стоимость документа ОказаниеУслуги, который нам больше не понадобится. Для этого откроем в конфигураторе окно редактирования объекта конфигурации Документ ОказаниеУслуги, перейдем на закладку Данные, раскроем список реквизитов табличной части документа, выделим реквизит
Стоимость и нажмем кнопку Удалить в командной панели.

Также следует удалить соответствующее поле из таблицы ПереченьНоменклатуры, расположенной в форме.
Для этого откроем форму ФормаДокумента документа ОказаниеУслуги и в окне структуры элементов формы выделим поле таблицы ПереченьНоменклатурыСтоимость и нажмем кнопку Удалить в командной панели.

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

Теперь изменим запрос таким образом, чтобы он создавал временную таблицу, которая будет храниться в нашем менеджере временных таблиц МенеджерВТ. Чтобы конструктор запроса смог открыть наш запрос, удалим из него строку (поля Стоимость у нас больше нет)
[| МАКСИМУМ (ОказаниеУслугиПереченьНоменклатуры.
Стоимость) КАК Стоимость]
Также удалим запятую в конце предыдущей строки.
Теперь установим курсор внутрь текста запроса, например на слове ВЫБРАТЬ, и выполним команду контекстного меню Конструктор запроса. Существующий текст запроса будет показан в форме конструктора запросов

Чтобы результат запроса поместить во временную таблицу, перейдем на закладку Дополнительно и отметим пункт Создание временной таблицы. Зададим имя временной таблицы – НоменклатураДокумента.

Нажмем ОК и посмотрим, какой текст сформировал конструктор запроса. Новым здесь является только строка [|ПОМЕСТИТЬ НоменклатураДокумента].
Это означает, что результат запроса будет сохранен во временной таблице НоменклатураДокумента. Теперь если мы для другого запроса укажем этот же самый менеджер временных таблиц МенеджерВТ, то в этом другом запросе мы сможем обратиться к данным этой временной таблицы. Таким образом, мы выполнили первую часть нашего плана – создали запрос, помещающий данные табличной части документа во временную таблицу.

Теперь займемся конструированием второго запроса. Установим курсор на следующую строку после оператора РезультатЗапроса = Запрос.Выполнить(); (именно здесь выполняется создание временной таблицы) и напишем заготовку будущего запроса.

Мы создали новый объект Запрос и назначили ему тот же самый менеджер временных таблиц, чтобы иметь возможность обращаться к созданной нами ранее временной таблице. Теперь установим курсор внутрь кавычек и выполним команду контекстного меню Конструктор запроса. Согласимся на создание нового запроса. Поскольку мы собираемся выбирать данные из нашей временной таблицы, создадим в запросе описание этой временной таблицы. Для этого над списком Таблицы нажмем кнопку Создать описание временной таблицы

В открывшемся окне введем имя нашей временной таблицы НоменклатураДокумента и добавим описание полей:
- Номенклатура, тип СправочникСсылка.Номенклатура;
- ВидНоменклатуры, тип
- ПеречислениеСсылка.ВидыНоменклатуры;
- КоличествоВДокументе, тип Число, 15, 3;
- СуммаВДокументе, тип Число, 15, 2.

Нажмем ОК. Выберем из этой таблицы все поля и нажмем кнопку Запрос в левом нижнем углу окна конструктора запроса.


Итак, мы создали первую часть второго запроса – выбрали информацию из временной таблицы.

Теперь будем соединять эту конструкцию левыми соединениями с таблицами остатков.
Начнем со стоимости материалов.
Добавим в список таблиц запроса виртуальную таблицу РегистрНакопления.СтоимостьМатериалов.Остатки. Из нее выберем поле СтоимостьОстаток. Перейдем на закладку Связи и зададим связь между таблицами.
Из временной таблицы будем выбирать все записи, и поле Номенклатура временной таблицы должно быть равно полю Материал таблицы остатков

Нужно не забыть ограничить виртуальную таблицу только той номенклатурой, которая есть в нашей временной таблице.
Поэтому вернемся на закладку Таблицы и поля, выделим в списке таблиц таблицу СтоимостьМатериаловОстатки и нажмем кнопку Параметры виртуальной таблицы, расположенную над списком таблиц.
Зададим параметр Условие следующим образом

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

Тем самым мы добавили к выбранным ранее полям стоимость номенклатуры:

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

Также зададим параметры виртуальной таблицы ОстаткиМатериаловОстатки.
В параметр Условие внесем следующий текст:

В результате мы получим следующий текст запроса

Тем самым мы добавили к выбранным ранее полям остатки номенклатуры на всех складах.

В заключение перейдем на закладку Объединения/Псевдонимы и зададим следующие псевдонимы полей:
- СтоимостьОстаток – Стоимость;
- КоличествоОстаток – Количество.

В результате мы получим следующий текст запроса

В качестве последнего штриха нашей работы с запросом нужно предусмотреть тот случай, когда номенклатура в справочнике есть, но у нее нет ни остатков, ни стоимости. Это может быть, например, в том случае, когда номенклатуру создали в справочнике, но она еще не поступала в нашу фирму. В такой ситуации левые соединения с виртуальными таблицами не вернут ничего. На языке запросов это значит, что в полях Стоимость и Количество будут значения NULL. Чтобы в дальнейшем нам было удобно работать с результатом нашего запроса, сразу же в самом запросе избавимся от этих значений. Для этого мы применим функцию ЕСТЬNULL() к полям
Стоимость и Количество. Если значение этого поля будет NULL, функция вернет 0. В остальных случаях функция вернет само значение этого поля. Перейдем на закладку Таблицы и поля, выделим поле СтоимостьМатериаловОстатки.СтоимостьОстаток и нажмем кнопку Изменить текущий элемент

В открывшемся окне отредактируем значение поля следующим образом

Аналогично поступим и с другим полем: ОстаткиМатериаловОстатки.КоличествоОстаток. Нажмем ОК – текст запроса будет вставлен в модуль.

Нам останется всего лишь дописать после него оператор выполнения запроса

Теперь разберемся с записью движений. Все операторы, которые были написаны нами ранее, будут работать без изменений. Единственное, что потребуется изменить, – это способ получения стоимости. Раньше мы просто брали ее из документа, теперь же нам нужно ее рассчитать на основании тех данных, которые мы получили запросом. Стоимость материала равна частному от деления всей стоимости, полученной запросом (Стоимость), на общее количество материала на всех складах (Количество). Но, как мы уже сказали, возможна ситуация, когда поле Количество у нас будет равно 0, а на
0 делить нельзя. Поэтому сразу после начала цикла обхода результата запроса рассчитаем стоимость для текущей номенклатуры

Теперь заменим расчет стоимости в движениях регистров СтоимостьМатериалов и Продажи:


Теперь все вроде бы хорошо, но остался один важный момент. Если проводить этот документ в первый раз, то результат получится правильный. Однако если документ был проведен ранее, и мы заново решим провести его, мы получим неправильный результат. Дело в том, что когда мы находимся в обработчике проведения документа и этот документ был уже проведен ранее, то в базе данных существуют движения этого документа. Таким образом, читая из базы данных стоимость и остатки материалов, мы прочитаем их с учетом тех движений, которые документ выполнил ранее. А это неправильно. Чтобы в обработчике проведения документа прочитать данные базы данных без учета предыдущих движений, которые мог выполнять документ, нужно перед чтением записать пустые наборы записей в те регистры, из которых мы собираемся читать. В нашем случае такими регистрами являются регистры накопления СтоимостьМатериалов и ОстаткиМатериалов. Поэтому перед выполнением второго запроса добавим следующие две строки

В режиме «1С:Предприятие»
Теперь нужно запустить «1С:Предприятие» в режиме отладки, перепровести все документы Оказание услуги и проверить, что данные правильно заносятся в регистры.
Теория – Как быстро посмотреть результат запроса
В процессе изменения процедуры проведения документа ОказаниеУслуги мы с вами подошли уже к написанию довольно сложных запросов. Зачастую возникает необходимость быстро убедиться в правильности тех данных, которые читаются из базы данных с помощью запроса, просмотреть их в виде таблицы. Есть простой способ сделать это в конфигураторе. Рассмотрим его на примере запроса из нашей обработки проведения. Предположим, после выполнения запроса (Запрос2) нужно выгрузить результат запроса в таблицу значений (ТЗ). Это можно сделать следующим образом:
После этого нужно установить точку останова на следующем операторе (ВыборкаДетальныеЗаписи = Результат.Выбрать()). Затем запустить «1С:Предприятие» в режиме отладки и перепровести один из документов Оказание услуги. Например, документ № 2. После того как исполнение кода будет остановлено, нужно двойным щелчком выделить слово ТЗ и нажать кнопку Вычислить выражение (Shift + F9) на панели
инструментов Отладка конфигурации. Откроется окно просмотра выражений, в котором будет находиться наша таблица значений ТЗ.
Таблица значений является коллекцией, поэтому, чтобы просмотреть ее содержимое, выделим строку ТЗ в окне Результат и нажмем кнопку Показать значения в отдельном окне (или F2) над окном результата:

Мы увидим всю таблицу значений, которая будет содержать результат выполнения нашего запроса. С помощью кнопки Вывести список можно вывести эту таблицу значений, например, в табличный документ, если требуется какой-то анализ этих данных или если эти данные нужно сохранить для сравнения. После просмотра таким образом результата запроса нужно не забыть снять точку останова в процедуре проведения документа и закомментировать (или удалить) добавленную нами строку, выгружающую результат запроса в таблицу значений, поскольку для нормальной работы документа она не нужна.
Оперативное и неоперативное проведение документов
Теперь мы поговорим о некоторых особенностях, связанных с тем, что в обработке проведения мы будем контролировать наличие достаточного количества материалов на складе при проведении документа. Делать это мы будем не всегда. А только в том случае, когда документ проводится оперативно. Если документ проводится неоперативно, то мы это делать не будем. Так для чего же предназначено оперативное и неоперативное проведение документа? Как устроен в системе механизм оперативногопроведения документов? При разработке конфигураций на платформе
«1С:Предприятие» используется концепция оперативного и неоперативного проведения документов. Эта концепция используется при решении задач оперативного учета (например, при складском учете товаров) и позволяет организовать корректную работу с документами в условиях реальной действительности. Однако можно отключить этот механизм (запретить оперативное проведение документов) и реализовывать собственные алгоритмы, проверять актуальность документов на оси событий и т.д. Эта концепция подразумевает, что работа пользователей может происходить в двух принципиально разных по своей сути режимах.
Оперативное проведение документов пользователями выполняется в режиме «реального времени», то есть отображает изменения, факты, свершающиеся в настоящее время. Оперативное проведение особенно актуально при многопользовательской работе. Поэтому при этом способе проведения документов следует осуществлять максимум проверок, способных исключить ошибки при вводе данных пользователями. Например, при оперативном проведении следует выполнять контроль остатков на складе списываемой номенклатуры с тем, чтобы исключить одновременную продажу одного товара несколькими продавцами. При оперативном проведении документа система, прежде всего, проверит положение даты документа относительно текущей даты сеанса. Текущая дата сеанса равна системной дате компьютера, приведенной к часовому поясу сеанса. Если дата проводимого документа совпадает с текущей датой сеанса, то система будет проводить такой документ в оперативном режиме, и в обработке проведения об этом можно узнать, чтобы выстроить определенный алгоритм проведения документа. Если дата проводимого документа меньше текущей даты сеанса, то такой документ система будет проводить в неоперативном режиме. Неоперативное проведение документов подразумевает отражение в базе данных фактов, которые свершились в прошлом или которые точно будут совершены в будущем. Поэтому задача неоперативного проведения
документов – просто отразить в информационной базе данные о совершенных операциях. При неоперативном проведении документов не имеет смысла производить целый ряд проверок, в частности контроль остатков. Подразумевается, что если в процессе неоперативного проведения документов были допущены ошибки (например, списано такое количество номенклатуры, которого не было на складе на дату проведения документа), то анализ полученного состояния базы данных является отдельной задачей, не относящейся к неоперативному проведению и выполняющейся не в момент проведения документа, а тогда, когда в базе имеются достаточные данные для анализа, например, когда введены более ранние документы, приходующие товары. Таким образом, оперативное проведение служит для того, чтобы в реальном режиме многопользовательской работы определить возможность или невозможность выполнения той или иной операции (и выполнить ее, если возможно). Неоперативное проведение предназначено для безусловного отражения в базе операций, которые уже были совершены (или точно будут совершены). Зачастую возникает желание провести документ будущей датой, чтобы отразить какие-то события, которые точно наступят в будущем. В этом случае если документу разрешено использовать оперативное проведение, то система не даст провести такой документ будущей датой. Она просто сообщит, что не может оперативно провести такой документ, и не даст никаких вариантов выбора. Пользователю останется только поменять дату документа или на текущую дату (тогда документ будет проведен в оперативном режиме), или прошедшую (тогда документ будет проведен в неоперативном режиме). Поэтому если логика учета подразумевает, что какой-то документ должен проводиться будущей датой, для такого документа механизм оперативного проведения должен быть отключен в метаданных (на закладке Движения окна редактирования объекта конфигурации). С оперативным проведением документов связано понятие оперативной отметки времени и понятие момента времени. Прямо
сейчас нам эта информация не понадобится, но раз уж зашла речь об оперативном проведении, есть подходящий случай рассказать об этом.
Понятие момента времени
Для определения положения документа на оси времени используется реквизит документа Дата. Дата содержит время с точностью до секунды. Это позволяет контролировать последовательность записи документов. Однако при большом объеме создаваемых документов вероятна ситуация, когда несколько документов будут иметь одинаковое значение даты (т. е. будут созданы в течение одной секунды). Как в этом случае определить последовательность созданных документов? Для обработки подобных ситуаций было введено понятие момент времени. Момент времени представляет собой совокупность даты, времени и ссылки на объект базы данных. Он позволяет однозначно идентифицировать любой объект ссылочного типа базы данных на оси событий, но имеет смысл в основном только для документов. Кроме того, момент времени позволяет идентифицировать и необъектные данные, например, записи регистров, подчиненных регистратору. Понятие момента времени реализовано во встроенном языке при помощи универсального объекта МоментВремени. Этот объект имеет свойства Дата и Ссылка, которые позволяют получить«составляющие» момента времени, и один метод – Сравнить(), при помощи которого возможно сравнение двух моментов времени между собой. Кроме этого, объект МоментВремени имеет конструктор и может быть создан в явном виде для любого объекта базы данных ссылочного типа. Для нескольких документов, имеющих одинаковую дату и время, последовательность их на оси событий определяется системой исходя из ссылок на эти документы. Она может не совпадать с последовательностью создания документов, и она недоступна для изменения пользователем, то есть нельзя каким-либо образом повлиять на последовательность документов внутри одной секунды или вычислить, что один документ создан раньше, а другой – позже. Оперативная отметка времени создается системой каждый
раз при оперативном проведении документа. Ее значение формируется исходя из текущей даты сеанса и последней созданной оперативной отметки. Если последняя оперативная отметка меньше текущей даты сеанса, в качестве новой оперативной отметки принимается текущая дата сеанса.
Если последняя оперативная отметка равна или больше текущей даты сеанса, в качестве новой оперативной отметки принимается значение на одну секунду большее, чем старая оперативная отметка времени. Таким образом, если у объекта конфигурации Документ установлено свойство оперативного проведения, последовательность действий системы будет следующей:
- при создании нового документа система будет устанавливать ему текущую дату сеанса и «нулевое» время;
- при проведении такого документа (с датой, день которой соответствует дню текущей даты сеанса) система установит в качестве даты документа оперативную отметку времени;
- если отменить проведение документа и затем провести его снова (не изменяя даты), система установит документу новую оперативную отметку времени;
- если попытаться перепровести документ, то система также автоматически установит документу новую оперативную отметку времени и проведет его;
- при попытке проведения (или перепроведения) оперативно проводимого документа с датой, день которой меньше дня текущей даты сеанса, документ будет проведен неоперативно;
- если попытаться провести (или перепровести) оперативно проводимый документ с датой, день которой больше дня текущей даты сеанса, то система не даст выполнить такое действие.

Способы доступа к данным в 1С:Предприятие