С-Предприятие 8.0. Практическое пособие разработчика

         

Общие сведения об анализе и прогнозировании данных


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

Прогнозирование позволяет, на основе проведенного анализа данных, предсказать последующие события или значения некоторых характеристик для новых исходных данных.

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

Например, в нашей базе имеются данные о том, какие товары и когда приобретались нашими клиентами. Было замечено, что если клиент приобретал, скажем, диван "Сказка", то через некоторое время он, зачастую, приобретал и кресло "Сказка", из того же набора мягкой мебели.

Мы хотим проанализировать данные нашей базы в следующем виде: какие существуют наиболее вероятные последовательности покупок товаров одним и тем же клиентом? Иначе говоря – "как обстоят дела сейчас, если взять текущие данные и попробовать определить, какие существуют последовательности покупаемых товаров"? [445]

В терминах 1С:Предприятия 8.0 такой процесс анализа данных можно представить следующей схемой:

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

Тип анализа – определяет вид результата, к которому должны быть сведены исходные данные. Система поддерживает пять различных типов анализа:

·общая статистика – позволяет получить общую статистическую информацию об источнике данных для его предварительного исследования (количество значений и количество уникальных значений, минимальное, максимальное и среднее значение, размах, стандартное отклонение, медиана, мода, частота появления каждого значения в источнике), [446]
·поиск ассоциаций предназначен для поиска часто встречаемых групп характеристик объектов и создания правил ассоциации "Если ... To ..." (например, такой анализ может быть использован для поиска групп товаров, часто покупаемых вместе),
·поиск последовательностей – применяется для выявления цепочек событий часто наблюдаемых в источнике данных (например, это может быть цепочка товаров или услуг, которые часто последовательно приобретают клиенты),
·дерево решений – предназначен для выявления закономерностей того, что объект относится к тому или иному классу (например, при помощи дерева решения можно проанализировать какие характеристики клиента влияют на то, что он перейдет к другому поставщику),
·кластерный анализ – при помощи кластерного анализа можно объединить объекты в группы (кластеры), в которых будут находиться объекты, наиболее схожие по ряду характеристик. Например, можно сгруппировать клиентов по их характеристикам и деятельности, чтобы в дальнейшем проанализировав полученные кластеры принять решение о стратегии работы с клиентами определенных групп.




Анализ данных – объект встроенного языка (АнализДанных), непосредственно выполняющий анализ данных. Объекту устанавливается источник данных, задаются параметры, настраиваются колонки анализа данных. Результатом работы данного объекта является результат анализа данных, тип которого зависит от типа анализа. Каждому типу анализа соответствует свой тип результата анализа.

Результат анализа – объект встроенного языка, содержащий информацию о результате анализа. Для каждого типа анализа предусмотрен свой тип результата. Например, результатом анализа данных типа АнализДанныхДеревоРешений будет объект типа РезультатАнализаДанныхДеревоРешений.

В дальнейшем результат может быть выведен в табличный Документ при помощи построителя отчета анализа данных (о нем будет рассказано далее), может быть выведен посредством программного доступа к его содержимому, может быть использован [447] для создания модели прогноза. Любой результат анализа данных может быть сохранен для последующего использования.

Теперь рассмотрим, как выглядит прогнозирование данных Прогнозирование является попыткой предсказать новый результат, на основе некоторой совокупности новых данных и определенной ранее модели. Иными словами, прогнозирование позволяет ответить на вопрос: "как будут обстоять дела, если мы будем иметь такие данные при такой модели их взаимосвязи"?

Возвращаясь к нашему примеру – "какой товар, с большой долей вероятности, клиент приобретет в следующий раз, если до этого он совершал вот такие покупки, и текущие последовательности покупок товаров выглядят следующим образом"?

В терминах 1С:Предприятия 8.0 этот процесс прогнозирования данных можно представить следующей схемой:

[448]

Источник данных – таблица значений, результат запроса или область табличного документа, содержащая информацию, по которой необходимо построить прогноз. Например, для модели прогноза МодельПрогнозаПоискАссоциаций, выборка может содержать перечень товаров документа продажи. Результат же работы модели может рекомендовать, какие товары можно еще предложить покупателю.





Модель прогноза – специальный объект, позволяющий выполнять прогноз на основании входных данных. Тип модели зависит от типа анализа данных. Например, модель, созданная для анализа данных АнализДанныхПоискАссоциаций будет иметь тип МодельПрогнозаПоискАссоциаций. Такая модель сможет выдавать прогнозы типа: "т.к. данный покупатель купил заданный набор товаров, то с определенной вероятностью он должен купить и другой набор товаров". На вход модели прогноза передается источник данных для прогноза. Результатом является таблица значений, содержащая прогнозируемые значения.

Таблица значений – таблица значений, состоящая из колонок, согласно настройкам результирующих колонок модели прогноза, содержащая прогнозируемые данные. Конкретное содержание таблицы определяется типом анализа данных. [449]

Если обобщить обе схемы, представленные выше, то анализ и прогнозирование данных в терминах 1С:Предприятия 8.0 можно представить следующим образом:



[450]

Как вы видите, на этой схеме появились новые прямоугольники. Мы рассмотрим их назначение в следующем разделе.


Общие сведения об обмене данными


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

Такая гибкость обмена данными достигается тем, что средства обмена данными 1С:Предприятия 8.0 могут использоваться в различных комбинациях, и кроме этого, формат обмена данными основан на языке XML, являющимся на сегодняшний день общепринятым средством представления данных.

К механизмам обмена данными могут быть отнесены:

·Планы обмена,
·XML-сериализация,
·Средства чтения и записи документов XML.

В общем случае схема взаимодействия этих трех составляющих может быть представлена следующим образом:

При помощи планов обмена мы получаем информацию о том, какие элементы данных были изменены, и в какой узел обмена их необходимо передать. Это возможно благодаря тому, что планы обмена содержат механизм регистрации изменений. Информация об [394] измененных данных переносится с помощью сообщений, инфраструктура которых также поддерживается планами обмена.

XML-сериализация позволяет преобразовать объект 1С:Предприятия в последовательность данных, представленных в формате XML. Кроме этого, XML-сериализация выполняет и обратное преобразование – преобразует последовательность данных формата XML в объект 1С:Предприятия, при условии, что имеется соответствующий тип 1С:Предприятия.

Запись и чтение документов XML обеспечивают запись/чтение документов формата XML из встроенного языка.

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

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



Оптимизация документа ОказаниеУслуги


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

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

После этого можно полностью удалить содержимое обработчика события "ОбработкаПроведения" в модуле документа и создать в нем заготовку процедуры проведения. Текст запроса, выполняемого в режиме оперативного проведения, будет отличаться от запроса, выполняемого при неоперативном проведении, поэтому формирование текста запроса мы включим в условие Если...Иначе...КонецЕсли:

Процедура ОбработкаПроведения(Отказ, Режим)

   Запрос = Новый Запрос;

   Если Режим = РежимПроведенияДокумента.Оперативный Тогда

       Запрос.Текст =

       ;

   Иначе

       Запрос.Текст =

       ;

   КонецЕсли;

КонецПроцедуры

Вызовем конструктор запроса и раскроем таблицу "ПереченьНоменклатуры" табличной части документа "ОказаниеУслуги" и выберем из нее поля:

·"Номенклатура",
·"Количество",
·"ВидНоменклатуры",
·"Сумма".[238]

Эти поля будут нужны нам для задания значений измерений регистров и их ресурсов. Кроме того, поле "ВидНоменклатуры" понадобится нам для анализа того, чем является номенклатура, указанная в документе: материалом или услугой:

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

Поэтому добавим к списку выбранных таблиц еще две таблицы:

·"РегистрНакопления.СтоимостьМатериалов.Остатки",
·"РегистрНакопления.ОстаткиМатериалов.Остатки":






Для этих виртуальных таблиц нам нужно будет задать параметры. Эти параметры будут одинаковы для обеих таблиц. Они будут включать в себя момент времени, на который должны быть получены остатки этих регистров, и условие получения данных.[239]

Условие получения данных указывает, что остатки должны быть получены только по тем позициям номенклатуры, которые содержатся в проводимом документе (перед выполнением запроса мы передадим в параметр "СписокНоменклатурыДокумента" список всех позиций номенклатуры, содержащихся в проводимом документе):





Узнай больше!

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

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

Очевидно, что второй вариант будет работать дольше, и время выполнения такого запроса будет зависеть в основном не от количества данных, содержащихся в документе (т.е. реального количества обрабатываемой информации), а от размера регистра накопления.[240]

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



После того, как будут заданы параметры обеих виртуальных таблиц регистров накопления, выберем из них поля "СтоимостьОстаток" и "КоличествоОстаток":



Теперь вспомним о том, что документы "ОказаниеУслуги" могут быть проведены как в оперативном, так и в неоперативном режиме.



Узнай больше!

О концепции оперативного и неоперативного проведения документов можно прочитать в главе "Концепция оперативного и неоперативного проведения документов" на странице 541.

Поскольку в оперативном режиме нам понадобится контролировать остатки списываемой номенклатуры на складе, выберем еще раз виртуальную таблицу регистра накопления "ОстаткиМатериалов" и переименуем ее в "ОстаткиМатериаловОстаткиНаСкладе":



[241]

Для этой виртуальной таблицы мы также укажем "МоментВремени", а в условии напишем, что материал должен находиться в списке номенклатуры и склад должен быть равен складу указанному в документе:



Теперь из этой виртуальной таблицы мы выберем поле "КоличествоОстаток":



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





[242]

Теперь перейдем на закладку "Дополнительно" и установим флаг "Для изменения". Предложение ДЛЯ ИЗМЕНЕНИЯ позволяет заблаговременно заблокировать чтение указанных данных (которые могут читаться транзакцией другого соединения) уже при считывании, чтобы исключить взаимные блокировки при записи. Это предложение дает возможность указать в запросе таблицы, считываемые данные которых предполагается изменять.





Узнай больше!

Более подробно об использовании предложения ДЛЯ ИЗМЕНЕНИЯ можно прочесть на диске ИТС (информационно-технологического сопровождения) в статье "Использование предложения ДЛЯ ИЗМЕНЕНИЯ в языке запросов".

Поскольку мы с вами планируем выполнить запись регистров накопления "ОстаткиМатериалов" и "СтоимостьМатериалов", укажем таблицы этих регистров в качестве таблиц для изменения:



Перейдем на закладку "Условия" и зададим условие отбора из таблицы документа только строк проводимого документа (ссылка на него будет передана в параметр запроса "Ссылка"):



[243]

Перейдем на закладку "Псевдонимы" и зададим следующие псевдонимы полей:

·"НоменклатураВидНоменклатуры" – "ВидНоменклатуры",
·"КличествоОстаток1> – "КоличествоОстатокНаСкладе".
Нажмем "ОК" и посмотрим, какой текст запроса сформировал конструктор:

Процедура ОбработкаПроведения(Отказ, Режим)

   Запрос = Новый Запрос;

   Если Режим = РежимПроведенияДокумента.Оперативный Тогда

       Запрос.Текст =

       "ВЫБРАТЬ

       |    ОказаниеУслугиПереченьНоменклатуры.Номенклатура,

       |    ОказаниеУслугиПереченьНоменклатуры.Количество,

       |    ОказаниеУслугиПереченьНоменклатуры.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,

       |    ОказаниеУслугиПереченьНоменклатуры.Сумма,

       |    ОстаткиМатериаловОстатки.КоличествоОстаток,

       |    СтоимостьМатериаловОстатки.СтоимостьОстаток,

       |    ОстаткиМатериаловОстаткиНаСкладе.КоличествоОстаток КАК КоличествоОстатокНаСкладе



       |ИЗ

       |    Документ.ОказаниеУслуги. ПереченьНоменклатуры КАК ОказаниеУслугиПереченьНоменклатуры

       |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(&МоментВремени, Материал В (&СписокНоменклатурыДокумента)) КАК СтоимостьМатериаловОстатки

       |        ПО ОказаниеУслугиПереченьНоменклатуры.Номенклатура = СтоимостьМатериаловОстатки.Материал

       |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(&МоментВремени, Материал В (&СписокНоменклатурыДокумента)) КАК ОстаткиМатериаловОстатки

       |        ПО ОказаниеУслугиПереченьНоменклатуры.Номенклатура = ОстаткиМатериаловОстатки.Материал

       |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(

       |        &МоментВремени,

       |        Материал В (&СписокНоменклатурыДокумента)

       |            И Склад = &СкладВДокументе) КАК ОстаткиМатериаловОстаткиНаСкладе

       |        ПО ОказаниеУслугиПереченьНоменклатуры.Номенклатура = ОстаткиМатериаловОстаткиНаСкладе.Материал

       |ГДЕ [244]

       |    ОказаниеУслугиПереченьНоменклатуры.Ссылка = &Ссылка

       |

       |ДЛЯ ИЗМЕНЕНИЯ

       |    РегистрНакопления.СтоимостьМатериалов.Остатки,



       |    РегистрНакопления.ОстаткиМатериалов.Остатки";

   Иначе

       Запрос.Текст =

       ;

   КонецЕсли;

КонецПроцедуры

Как видите, в запросе нет ничего сложного за исключением, быть может, трех левых соединений с таблицей табличной части документа и использования ключевого предложения ДЛЯ ИЗМЕНЕНИЯ, значение которого было объяснено выше.

Текст запроса для случая неоперативного проведения документа будет практически таким же, за исключением того, что в нем будет отсутствовать третье левое соединение и, соответственно, поле "КоличествоОстатокНаСкладе", т.к. проверку остатков в этом случае мы выполнять не будем:

...

   Иначе

       Запрос.Текст =

       "ВЫБРАТЬ

       |    ОказаниеУслугиПереченьНоменклатуры.Номенклатура,

       |    ОказаниеУслугиПереченьНоменклатуры.Количество,

       |    ОказаниеУслугиПереченьНоменклатуры.Номенклатура.ВидНоменклатуры КАК ВидНоменклатуры,

       |    ОказаниеУслугиПереченьНоменклатуры.Сумма,

       |    ОстаткиМатериаловОстатки.КоличествоОстаток,

       |    СтоимостьМатериаловОстатки.СтоимостьОстаток

       |ИЗ

       |    Документ.ОказаниеУслуги.ПереченьНоменклатуры КАК ОказаниеУслугиПереченьНоменклатуры

       |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(&МоментВремени, Материал В (&СписокНоменклатурыДокумента)) КАК СтоимостьМатериаловОстатки



       |        ПО ОказаниеУслугиПереченьНоменклатуры.Номенклатура = СтоимостьМатериаловОстатки.Материал

       |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(&МоментВремени, Материал В (&СписокНоменклатурыДокумента)) КАК ОстаткиМатериаловОстатки

       |        ПО ОказаниеУслугиПереченьНоменклатуры.Номенклатура = [245] ОстаткиМатериаловОстатки.Материал

       |ГДЕ

       |    ОказаниеУслугиПереченьНоменклатуры.Ссылка = &Ссылка

       |

       |ДЛЯ ИЗМЕНЕНИЯ

       |    РегистрНакопления.СтоимостьМатериалов.Остатки,

       |    РегистрНакопления.ОстаткиМатериалов.Остатки";

   КонецЕсли;

КонецПроцедуры

Теперь добавим в текст обработчика задание параметров запроса:

Процедура ОбработкаПроведения(Отказ, Режим)

   Запрос = Новый Запрос;

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

...

       |    РегистрНакопления.ОстаткиМатериалов.Остатки";

   КонецЕсли;

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

   Запрос.УстановитьПараметр("СписокНоменклатурыДокумента", ПереченьНоменклатуры.ВыгрузитьКолонку("Номенклатура"));

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

...

КонецПроцедуры

Обратите внимание, что для формирования списка номенклатуры документа мы используем метод ВыгрузитьКолонку() объекта ДокументТабличнаяЧасть.ОказаниеУслуги.ПереченьНоменклатуры.[246]

После этого добавим получение результата запроса и цикл его обхода:



...

   КонецЕсли;

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

   Запрос.УстановитьПараметр("СписокНоменклатурыДокумента", ПереченьНоменклатуры.ВыгрузитьКолонку("Номенклатура"));

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

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

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

   КонецЦикла;

КонецПроцедуры

Теперь, прежде чем начать формирование движений по регистрам, нам нужно проверить наличие на складе достаточного количества номенклатуры:

...

   КонецЕсли;

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

   Запрос.УстановитьПараметр("СписокНоменклатурыДокумента", ПереченьНоменклатуры.ВыгрузитьКолонку("Номенклатура"));

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

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

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

       //Проверить остаток при оперативном проведении

       Если Режим = РежимПроведенияДокумента.Оперативный Тогда

           Если ВыборкаРезультатаЗапроса.ВидНоменклатуры = Перечисления.ВидыНоменклатуры.Материал Тогда

               Остаток = ?(ВыборкаРезультатаЗапроса.КоличествоОстатокНаСкладе = Null, 0, ВыборкаРезультатаЗапроса.КоличествоОстатокНаСкладе);

               Если Остаток < ВыборкаРезультатаЗапроса.Количество Тогда

                   Сообщить("Материала " + СокрЛП(ВыборкаРезультатаЗапроса.Номенклатура) + " имеется только " + Остаток);



                   Отказ = Истина;

                   Возврат;

               КонецЕсли;

           КонецЕсли; [247]

       КонецЕсли;

   КонецЦикла;

КонецПроцедуры

И в заключение добавим формирование движений по регистрам накопления:

...

   КонецЕсли;

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

   Запрос.УстановитьПараметр("СписокНоменклатурыДокумента", ПереченьНоменклатуры.ВыгрузитьКолонку("Номенклатура"));

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

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

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

       //Проверить остаток при оперативном проведении

       Если Режим = РежимПроведенияДокумента.Оперативный Тогда

           Если ВыборкаРезультатаЗапроса.ВидНоменклатуры = Перечисления.ВидыНоменклатуры.Материал Тогда

               Остаток = ?(ВыборкаРезультатаЗапроса.КоличествоОстатокНаСкладе = Null, 0, ВыборкаРезультатаЗапроса.КоличествоОстатокНаСкладе);

               Если Остаток < ВыборкаРезультатаЗапроса.Количество Тогда

                   Сообщить("Материала " + СокрЛП(ВыборкаРезультатаЗапроса.Номенклатура) + " имеется только " + Остаток);



                   Отказ = Истина;

                   Возврат;

               КонецЕсли;

           КонецЕсли;

       КонецЕсли;

       //Сформировать движения

       Если ВыборкаРезультатаЗапроса.ВидНоменклатуры = Перечисления.ВидыНоменклатуры.Материал Тогда

           // регистр ОстаткиМатериалов Расход

           Движение = Движения.ОстаткиМатериалов.Добавить();

           Движение.ВидДвижения = ВидДвиженияНакопления.Расход;

           Движение.Период = Дата;

           Движение.Материал = ВыборкаРезультатаЗапроса.Номенклатура;

           Движение.Склад = Склад;

           Движение.Количество = ВыборкаРезультатаЗапроса.Количество;

           // регистр СтоимостьМатериалов Расход

           Движение = Движения.СтоимостьМатериалов.Добавить();

           Движение.ВидДвижения = ВидДвиженияНакопления.Расход; [248]

           Движение.Период = Дата;

           Движение.Материал = ВыборкаРезультатаЗапроса.Номенклатура;

           //расчитать стоимость материала



           СтоимостьМатериала = ?(ВыборкаРезультатаЗапроса.КоличествоОстаток = Null, 0,

               ВыборкаРезультатаЗапроса.СтоимостьОстаток / ВыборкаРезультатаЗапроса.КоличествоОстаток);

           Движение.Стоимость = СтоимостьМатериала * ВыборкаРезультатаЗапроса.Количество;

       КонецЕсли;

       // регистр Продажи

       Движение = Движения.Продажи.Добавить();

       Движение.Период = Дата;

       Движение.Номенклатура = ВыборкаРезультатаЗапроса.Номенклатура;

       Движение.Клиент = Клиент;

       Движение.Мастер = Мастер;

       Движение.Количество = ВыборкаРезультатаЗапроса.Количество;

       Движение.Выручка = ВыборкаРезультатаЗапроса.Сумма;

       Если ВыборкаРезультатаЗапроса.ВидНоменклатуры =    Перечисления.ВидыНоменклатуры.Материал Тогда

           Движение.Стоимость = СтоимостьМатериала * ВыборкаРезультатаЗапроса.Количество;

       Иначе

           Движение.Стоимость = 0;

       КонецЕсли;

   КонецЦикла;

   // записать движения регистров

   Движения.ОстаткиМатериалов.Записать();

   Движения.СтоимостьМатериалов.Записать();

   Движения.Продажи.Записать();

КонецПроцедуры

Запустим 1С:Предприятие в режиме отладки и проверим работу нового обработчика события "ОбработкаПроведения", перепроведя все документы "ОказаниеУслуги".



В заключение следует сделать небольшое отступление, которое касается задания параметров виртуальных таблиц, использовавшихся в наших запросах.[249]

Как в первом, так и во втором запросах мы использовали условие что материал должен находиться в списке значений, задаваемом одним из параметров запроса:

...

|    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СтоимостьМатериалов.Остатки(&МоментВремени, Материал В (&СписокНоменклатурыДокумента)) КАК СтоимостьМатериаловОстатки

...

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

...

|    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиМатериалов.Остатки(

|    &МоментВремени,

|    Материал В

|        (ВЫБРАТЬ РАЗЛИЧНЫЕ

|            ОказаниеУслугиПереченьНоменклатуры.Номенклатура

|        ИЗ

|            Документ.ОказаниеУслуги.ПереченьНоменклатуры КАК ОказаниеУслугиПереченьНоменклатуры

|        ГДЕ

|            ОказаниеУслугиПереченьНоменклатуры.Ссылка = &Ссылка))

...

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

Теперь мы можем на некоторое время отвлечься от запросов, с которыми мы достаточно "плотно" работали в этой главе, и обратить свое внимание на не менее интересные возможности, которые предоставляет разработчику платформа 1С:Предприятие 8.0.[250]


Основные сведения о распределенных информационных базах


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

Таким образом, любой узел этой структуры может иметь произвольное количество подчиненных узлов (в том числе и ни одного). Кроме этого все узлы, кроме одного, должны иметь по одному главному узлу, и один узел не будет иметь главного узла – это корневой узел.

Такое жесткое задание структуры узлов необходимо для определения порядка миграции изменений данных и изменений конфигурации.

Конфигурация может быть изменена только в узле, не имеющем главного узла (то есть в корневом узле). Изменения данных могут выполняться в любом узле.

Изменения конфигурации будут передаваться от главного к подчиненным узлам. Изменения данных могут передаваться между любыми связанными узлами. [427]

Разрешение коллизий также будет производиться исходя Из отношения "главный–подчиненный". Если изменения выполнены одновременно и в главном, и в подчиненном узле, при обмене данными будут приняты только изменения главного узла, а изменения подчиненного отвергнуты.

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

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



Особенности использования ссылочных данных


Термином "ссылочные данные" мы будем обозначать данные, хранящиеся в базе данных, доступ к которым возможен при помощи объектов встроенного языка вида Ссылка: СправочникСсылка<имя>, ДокументСсылка.<имя> и т.д. Для того чтобы дальнейшее изложение было более понятным, мы построим объяснение на примере получения ссылки на вид номенклатуры при проведении документа "ОказаниеУслуги".

Не все данные, хранящиеся в базе данных, являются ссылочными. Это связано с тем, что в модели данных 1С:Предприятия 8.0 существует деление на данные, представляющие объектные сущности (справочники, планы счетов, документы и т.д.), и данные, представляющие необъектные сущности (регистры сведений, регистры накопления и т.д.).

С точки зрения системы, некоторая совокупность объектных данных определяется не только значениями своих полей, но и самим фактом своего существования. Другими словами, удалив из базы некоторую совокупность объектных данных, мы не сможем вернуть систему в то же состояние, которое было до удаления. Даже если мы заново создадим ту же самую совокупность объектных данных с теми же самыми значениями полей, с точки зрения системы это будет ДРУГАЯ совокупность объектных данных. Каждую такую совокупность объектных данных, уникальную с точки зрения системы, называют объектом базы данных. Для того чтобы система могла отличить один объект базы данных от другого, каждый объект базы данных (совокупность объектных данных) имеет внутренний идентификатор. Различные объекты базы данных всегда будут иметь различные внутренние идентификаторы. Этот идентификатор хранится вместе с остальными данными объекта в специальном поле "Ссылка".

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

Таким образом, поскольку мы можем однозначно указать на каждый объект базы данных, у нас появляется возможность хранить [233] такой указатель в полях других таблиц базы данных, выбирать его в поле ввода, указывать в параметрах запроса при поиске по ссылке и т.д. Во всех этих случаях как раз и будет использоваться объект встроенного языка вида Ссылка. Фактически этот объект хранит только внутренний идентификатор, находящийся в поле "Ссылка".



в поле, хранящем реквизит табличной


Например, если взять наш документ "ОказаниеУслуги", то в поле, хранящем реквизит табличной части "Номенклатура" на самом деле находится внутренний идентификатор, указывающий на элемент справочника "Номенклатура":



Когда в обработчике события "ОбработкаПроведения" документа "ОказаниеУслуги" мы присваиваем значение реквизита "Номенклатура" табличной части какой-либо переменной, мы имеем дело с объектом ДокументОбъект.ОказаниеУслуги. Этот объект содержит в себе значения всех реквизитов документа и реквизитов его табличных частей. Поэтому обращение:

Движение.Материал = ТекСтрокаПереченьНоменклатуры.Номенклатура;

приводит к тому, что мы просто читаем данные, хранящиеся в оперативной памяти:



Однако когда мы обращаемся к виду номенклатуры как к реквизиту того элемента справочника, ссылка на который указана в табличной части документа:

Если ТекСтрокаПереченьНоменклатуры.Номенклатура.ВидНоменклатуры <> Перечисления.ВидыНоменклатуры.Материал Toгда[235]

произойдет буквально следующее:



Поскольку в объекте ДокументОбъект.ОказаниеУслуги есть только ссылка на элемент справочника "Номенклатура" и больше никаких данных об этом элементе нет, система возьмет эту ссылку и обратится по ней в кэш объектов, в надежде найти там данные того объекта, ссылка на который у нее есть. Если кэш объектов не будет иметь нужных данных, он обратится к базе данных с тем, чтобы прочитать все данные объекта, ссылкой на который он обладает. После того, как все данные, хранящиеся в реквизитах нужного элемента справочника и в реквизитах его табличных частей, будут считаны в кэш объектов, кэш объектов вернет запрашиваемую ссылку, хранящуюся в реквизите "ВидНоменклатуры" справочника "Номенклатура".[236]

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





Узнай больше!

Более подробно об устройстве кэша объектов можно прочитать в главе "Кэш объектов" на   странице 554.

Из всего вышесказанного можно сделать следующий вывод: если алгоритм проведения документа использует только те данные, которые присутствуют в реквизитах документа (и его табличных частей), вполне достаточно использовать конструктор движений документа (как это было у нас в случае с документом "ПриходнаяНакладная"). Если же в алгоритме проведения требуется анализировать дополнительные реквизиты объектов, ссылки на которые содержатся в документе, а также использовать результаты расчета итогов регистров, – следует использовать запросы для более быстрой выборки данных из базы данных.

To же самое справедливо в отношении выполнения любых участков программы, критичных по производительности. Механизм запросов лучше "читает" информационную базу и может за один раз выбрать все необходимые данные, поэтому, например, в типовых решениях вы практически не увидите использования объекта СправочникВыборка<имя>.[237]


Отчет ОстаткиМатериаловПоСвойствам


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

Создадим новый объект конфигурации Отчет и назовем его "ОстаткиМатериаловПоСвойствам". Запустим конструктор выходной формы, и займемся конструированием запроса.

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

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

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

Сначала мы создадим вторую часть нашего алгоритма – запрос к регистру накопления "ОстаткиМатериалов".[277]

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

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

Из виртуальной таблицы регистра накопления "ОстаткиМатериалов.ОстаткиИОбороты" выберем следующие поля:

·"ОстаткиМатериаловОстаткиИОбороты.Материал",
·"ОстаткиМатериаловОстаткиИОбороты.НаборСвойств,
·"ОстаткиМатериаловОстаткиИОбороты.КоличествоНачальныйОстаток",
·"ОстаткиМатериаловОстаткиИОбороты.КоличествоПриход",
·"ОстаткиМатериаловОстаткиИОбороты.КоличествоРасход",
·"ОстаткиМатериаловОстаткиИОбороты.КоличествоКонечныйОстаток":






После этого на закладке "Объединения/Псевдонимы" зададим псевдонимы числовых полей без слова "Количество":



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

На этом создание первого запроса завершено. Нажмем "ОК".

Откроем модуль формы и удалим элементы текста, которые нам не понадобятся. В процедуре "ДействияФормыОстаткиМатериаловПоСвойствамСформировать" [279] удалим второй параметр при вызове процедуры "ОстаткиМатериаловПоСвойствам":

Процедура ДействияФормыОстаткиМатериаловПоСвойствамСформировать(Кнопка)

//{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА(ОстаткиМатериаловПоСвойствам)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   ТабДок = ЭлементыФормы.ПолеТабличногоДокумента;

   ОстаткиМатериаловПоСвойствам(ТабДок);

   //}}КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА

КонецПроцедуры

После этого в процедуре "ОстаткиМатериаловПоСвойствам" соответствующим образом изменим строку объявления процедуры и затем удалим строку, устанавливающую параметр запроса:

Процедура ОстаткиМатериаловПоСвойствам(ТабДок) Экспорт

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ(ОстаткиМатериаловПоСвойствам)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Макет = ОтчетОбъект.ПолучитьМакет("ОстаткиМатериаловПоСвойствам");

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ОстаткиМатериаловОстаткиИОбороты.Материал,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.Материал),



   |    ОстаткиМатериаловОстаткиИОбороты.НаборСвойств,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.НаборСвойств),

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачальныйОстаток,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоПриход КАК Приход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоРасход КАК Расход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток

   |ИЗ

   |    РегистрНакопления.ОстаткиМатериалов.ОстаткиИОбороты(, , , , НаборСвойств В (&СписокСвойств)) КАК ОстаткиМатериаловОстаткиИОбороты";[280]

   //Запрос.УстановитьПараметр("СписокСвойств",СписокСвойств);

   Результат = Запрос.Выполнить();

...

Теперь мы можем перейти к созданию вложенного запроса, который будет передаваться в условие, заданное в параметре виртуальной таблицы.

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

В качестве исходных данных вложенного запроса выберем таблицу регистра сведений "ЗначенияСвойствНоменклатуры". Из нее выберем единственное поле – "ЗначенияСвойствМатериалов.НаборСвойств".

Зададим условия выборки. Прежде всего, владелец набора свойств должен быть равен переданному в параметре "Материал" материалу:

ЗначенияСвойствНоменклатуры.НаборСвойств.Владелед = &Материал

Затем укажем, что вид свойства должен быть равен переданному в параметре "ВидСвойства" значению:



ЗначенияСвойствНоменклатуры.ВидСвойства = &ВидСвойства [281]

И в заключение отметим, что значение свойства также будет задаваться параметром "Значение":

ЗначенияСвойствНоменклатуры.Значение = &Значение



Вложенный запрос готов. Теперь нажмем кнопку "Запрос", расположенную в нижней части окна конструктора запроса, выделим и скопируем текст запроса в буфер обмена Windows. Закроем окно с текстом запроса и нажмем "Отмена" в конструкторе запроса. Теперь вставим текст из буфера обмена вместо параметра в созданный нами ранее запрос:

Процедура ОстаткиМатериаловПоСвойствам(ТабДок) Экспорт

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ(ОстаткиМатериаловПоСвойствам)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Макет = ОтчетОбъект.ПолучитьМакет("ОстаткиМатериаловПоСвойствам");

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ОстаткиМатериаловОстаткиИОбороты.Материал,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.Материал),

   |    ОстаткиМатериаловОстаткиИОбороты.НаборСвойств,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.НаборСвойств),

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачальныйОстаток,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоПриход КАК Приход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоРасход КАК Расход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток

   |ИЗ

   |    РегистрНакопления.ОстаткиМатериалов.ОстаткиИОбороты(,,,,

   |        НаборСвойств В



   |            (ВЫБРАТЬ

   |                ЗначенияСвойствНоменклатуры.НаборСвойств [282]

   |            ИЗ

   |                РегистрСведений.ЗначенияСвойствНоменклатуры КАК ЗначенияСвойствНоменклатуры

   |            ГДЕ

   |                ЗначенияСвойствНоменклатуры.НаборСвойств.Владелец = &Материал

   |                И ЗначенияСвойствНоменклатуры.ВидСвойства = &ВидСвойства

   |                И ЗначенияСвойствНоменклатуры.Значение = &Значение)) КАК ОстаткиМатериаловОстаткиИОбороты";

   Результат = Запрос.Выполнить();

...

Маленькая доработка, которую нам останется сделать, будет заключаться в том, чтобы предусмотреть динамическое формирование текста запроса в зависимости от того, выбрано пользователем значение материала, или нет:

Процедура ОстаткиМатериаловПоСвойствам(ТабДок) Экспорт

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ(ОстаткиМатериаловПоСвойствам)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Макет = ОтчетОбъект.ПолучитьМакет("ОстаткиМатериаловПоСвойствам");

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ОстаткиМатериаловОстаткиИОбороты.Материал,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.Материал),



   |    ОстаткиМатериаловОстаткиИОбороты.НаборСвойств,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.НаборСвойств),

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачальныйОстаток,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоПриход КАК Приход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоРасход КАК Расход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток

   |ИЗ

   |    РегистрНакопления.ОстаткиМатериалов.ОстаткиИОбороты(,,,,

   |        НаборСвойств В

   |            (ВЫБРАТЬ

   |                ЗначенияСвойствНоменклатуры.НаборСвойств

   |            ИЗ

   |                РегистрСведений. ЗначенияСвойствНоменклатуры КАК ЗначенияСвойствНоменклатуры [283]

   |            ГДЕ

   |";

   Если Не Материал.Пустая() тогда

       Запрос.Текст = Запрос.Текст +

           "ЗначенияСвойствНоменклатуры.НаборСвойств.Владелец = &Материал И

           |";

   КонецЕсли;

   Запрос.Текст = Запрос.Текст +

   "                ЗначенияСвойствНоменклатуры.ВидСвойства = &ВидСвойства

   |                И ЗначенияСвойствНоменклатуры.Значение = &Значение)) КАК ОстаткиМатериаловОстаткиИОбороты";



   Результат = Запрос.Выполнить();

...

После этого добавим в текст модуля установку параметров запроса:

Процедура ОстаткиМатериаловПоСвойствам(ТабДок) Экспорт

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ(ОстаткиМатериаловПоСвойствам)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Макет = ОтчетОбъект.ПолучитьМакет("ОстаткиМатериаловПоСвойствам");

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ОстаткиМатериаловОстаткиИОбороты.Материал,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.Материал),

   |    ОстаткиМатериаловОстаткиИОбороты.НаборСвойств,

   |    ПРЕДСТАВЛЕНИЕ(ОстаткиМатериаловОстаткиИОбороты.НаборСвойств),

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоНачальныйОстаток КАК НачальныйОстаток,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоПриход КАК Приход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоРасход КАК Расход,

   |    ОстаткиМатериаловОстаткиИОбороты.КоличествоКонечныйОстаток КАК КонечныйОстаток

   |ИЗ

   |    РегистрНакопления.ОстаткиМатериалов.ОстаткиИОбороты(,,,,

   |        НаборСвойств В

   |            (ВЫБРАТЬ

   |                ЗначенияСвойствНоменклатуры.НаборСвойств

   |            ИЗ

   |                РегистрСведений.ЗначенияСвойствНоменклатуры КАК ЗначенияСвойствНоменклатуры



   |            ГДЕ

   |"; [284]

   Если Не Материал.Пустая() тогда

       Запрос.Текст = Запрос.Текст +

           "ЗначенияСвойствНоменклатуры.НаборСвойств.Владелец = &Материал И

           |";

   КонецЕсли;

   Запрос.Текст = Запрос.Текст +

   "                ЗначенияСвойствНоменклатуры.ВидСвойства = &ВидСвойства

   |                И ЗначенияСвойствНоменклатуры.Значение = &Значение)) КАК ОстаткиМатериаловОстаткиИОбороты";

   Если Не Материал.Пустая() тогда

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

   КонецЕсли;

   Запрос.УстановитьПараметр("ВидСвойства", ВидСвойства);

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

   Результат = Запрос.Выполнить();

...

Теперь последнее, что нам осталось сделать – это разместить в форме поля для ввода параметров запроса. [285]

Откроем форму отчета и разместим на ней три поля ввода:

"Материал" с типом СправочникСсылка.Номенклатура,

"ВидСвойства" с типом ПланВидовХарактеристикСсылка.СвойстваНоменклатуры,

"Значение" с типом Характеристика.СвойстваНоменклатуры:



Для поля ввода "Материал" установим свойство "Выбор групп и элементов" как "Элементы".

Для поля ввода "Значение" установим связь по типу с реквизитом "ВидСвойства". А для поля ввода "ВидСвойства" создадим обработчик события "ПриИзменении":

Процедура ВидСвойстваПриИзменении(Элемент)



   Значение = ВидСвойства.ТипЗначения.ПривестиЗначение(Значение);

КонецПроцедуры [286]

На этом создание отчета завершено. Запустим 1С:Предприятие в режиме отладки и посмотрим, какие результаты можно получить с помощью нашего отчета.

Сначала посмотрим, какие у нас есть материалы с сечением 2,5 мм2:



Затем посмотрим, какие у нас есть материалы черного цвета:



[287]

И в заключение, чтобы убедиться в правильности работы отчета посмотрим, сколько у нас электрических кабелей черного цвета:



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

Следует заметить, что пример, рассмотренный нами в этой главе, не является законченным решением для данной конфигурации. Мы лишь продемонстрировали возможность ведения такого учета. Для того чтобы наша конфигурация могла полноценно использовать свойства материалов, необходимо внести соответствующие изменения в остальные регистры, документы и некоторые отчеты. [288]


Отчет ПереченьУслуг


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

Создадим новый объект конфигурации Отчет "ПереченьУслуг". Перейдем на закладку "Макеты" и вызовем конструктов выходной формы.

Выберем объектную (ссылочную) таблицу справочника "Номенклатура" и виртуальную таблицу регистра сведений "Цены.СрезПоследних". Для того чтобы исключить неоднозначность имен в запросе, переименуем таблицу "Номенклатура" в "СпрНоменклатура".

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

·"СпрНоменклатура.Родитель",
·"СпрНоменклатура.Ссылка",
·"ЦеныСрезПоследних.Цена":

Перейдем на закладку "Связи" и сбросим флаг "Все" у таблицы регистра и установим его у таблицы справочника. [191]

На закладке "Условия" зададим условие выбора элементов справочника "Номенклатура" – выбираемые элементы должны соответствовать виду номенклатуры переданному в параметре запроса "ВидНоменклатуры":

На закладке "Объединения/Лсевдонимы" укажем, что поле "Родитель" будет иметь псевдоним "ГруппаУслуг", а поле "Ссылка" – "Услуга":

Перейдем на закладку "Итоги" и укажем, что группировка будет производиться по полю "ГруппаУслуг" с типом итогов "Элементы и иерархия", а значения суммируемых полей задавать не станем:

На закладке "Отчет" сбросим флаг "Использовать построитель отчета".

На закладке "Выходная форма" отметим, что тип параметра "ДатаОтчета" будет Дата, а параметр "ВидНоменклатуры" в форме редактироваться не будет. Нажмем "ОК". [192]




Откроем модуль формы и в процедуре "ПереченьУслуг" определим значение параметра запроса:

   Запрос.УстановитьПараметр("ВидНоменклатуры", Перечисления.ВидыНоменклатуры.Услуга);

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

Теперь рассмотрим текст запроса, сформированный конструктором:

   Запрос.Текст =

   "ВЫБРАТЬ

   |    СпрНоменклатура.Родитель КАК ГруппаУслуг,

   |    ПРЕДСТАВЛЕНИЕ(СпрНоменклатура.Родитель),

   |    СпрНоменклатура.Ссылка КАК Услуга,

   |    СпрНоменклатура.Представление,

   |    ЦеныСрезПоследних.Цена

   |ИЗ

   |    Справочник.Номенклатура КАК СпрНоменклатура

   |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены.СрезПоследних(&ДатаОтчета, ) КАК ЦеныСрезПоследних

   |        ПО ЦеныСрезПоследних.Номенклатура = СпрНоменклатура.Ссылка

   |ГДЕ

   |    СпрНоменклатура.ВидНоменклатуры = &ВидНоменклатуры

   |ИТОГИ ПО

   |    ГруппаУслуг ИЕРАРХИЯ";

Практически все конструкции, использованные в этом запросе, нам уже известны, за исключением ключевого слова ИЕРАРХИЯ, использованного в части описания итогов. Это ключевое слово позволяет рассчитывать итоги по иерархии справочника, однако поскольку в нашем случае какие либо итоги в отчете не нужны, мы использовали эту возможность для создания группировок по иерархии справочника "Номенклатура".

Теперь запустим 1С:Предприятие в режиме отладки и, прежде всего, откроем периодический регистр "Цены". [193]

Добавим в него еще одно значение для услуги "Диагностика" новая цена услуги на 01.04.2004 (это позволит нам протестировать отчет):



Теперь выполним отчет "Перечень услуг" по состоянию на 31.03.2004:



Наш отчет правильно отражает цену услуги "Диагностика" на 31.04 – 200pyб. [194]

Еще раз выполним отчет, но теперь уже на другую дату 01.04.2004:



Как видите, показана новая цена услуги "Диагностика" – 350 руб.

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


Отчет РеестрДокументовОказаниеУслуги


Первым отчетом, на основе которого мы начнем знакомиться с языком запросов, будет отчет "РеестрДокументовОказаниеУслуги". Этот отчет просто будет выводить список существующих в базе данных документов "ОказаниеУслуги" в порядке их дат и номеров.

Создадим в конфигураторе новый объект конфигурации Отчет "РеестрДокументовОказаниеУслуги". Перейдем на закладку "Макет" и запустим конструктор выходной формы.

В качестве источника данных для запроса выберем объектную (ссылочную) таблицу документов "ОказаниеУслуги". Из этой таблицы выберем следующие поля:

·"Дата",
·"Номер",
·"Склад",
·"Мастер",
·"Клиент":

Обратите внимание, что при выборе полей "Склад", "Мастер" и "Клиент" в список выбранных полей подбираются также поля "Склад.Представление", "Мастер.Представление" и "Клиент.Представление". Дело в том, что в общем случае подразумевается, что эти поля будут выводиться в ячейки табличного документа. Поскольку соответствующие поля "Склад", "Мастер" и "Клиент" являются ссылочными, то в случае, если в качестве значения параметра для вывода будет передано значение-ссылка, система будет выполнять дополнительный запрос для получения представления этого поля (которое и будет выведено в документ), в результате чего вывод [166] отчета замедлится. Поэтому система, при выборе ссылочных полей, предлагает сразу же включить в список выбранных полей и представления ссылочных полей, в расчете на то, что именно они и будут использованы для вывода в документ.

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

Перейдем на закладку "Отчет" и сбросим флаг "Использовать построитель отчета":

Сбросим флаг "Использовать построитель отчета"...






Нажмем "ОК". Конструктор сформирует форму отчета и макет. Откроем модуль формы и найдем в нем процедуру "РеестрДокументовОказаниеУслуги". В этой процедуре как раз [167] формируется текст запроса, который будет использован для получения интересующих нас данных:

Запрос.Текст =

"ВЫБРАТЬ

   |    ОказаниеУслуги.Дата КАК Дата,

   |    ОказаниеУслуги.Номер КАК Номер,

   |    ОказаниеУслуги.Склад,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Склад),

   |    ОказаниеУслуги.Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Мастер),

   |    ОказаниеУслуги.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Клиент)

   |ИЗ

   |    Документ.ОказаниеУслуги КАК ОказаниеУслуги

   |

   |УПОРЯДОЧИТЬ ПО

   |    Дата,

   |    Номер";

Текст запроса начинается, как мы говорили выше, с части описания запроса:

Запрос.Текст =

   "ВЫБРАТЬ

   |    ОказаниеУслуги.Дата КАК Дата,

   |    ОказаниеУслуги.Номер КАК Номер,

   |    ОказаниеУслуги.Склад,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Склад),

   |    ОказаниеУслуги.Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Мастер),

   |    ОказаниеУслуги.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Клиент)

   |ИЗ

   |    Документ.ОказаниеУслуги КАК ОказаниеУслуги

Описание запроса начинается с обязательного ключевого слова ВЫБРАТЬ. Затем следует список полей выборки, в котором описываются поля, которые должны содержаться в результате запроса. Этот список может содержать как собственно поля, так и некоторые выражения, вычисляемые на основе значений полей. [168]



После ключевого слова ИЗ указываются источники данных – исходные таблицы запроса, содержимое которых обрабатывается в запросе. В данном случае это объектная (ссылочная) таблица "Документ.ОказаниеУслуги". После ключевого слова КАК указывается псевдоним источника данных. В нашем случае это "ОказаниеУслуги". В дальнейшем к этому источнику данных можно будет обращаться в тексте запроса, используя псевдоним.

Такое обращение мы видим в описании полей выборки:

   "ВЫБРАТЬ

   |    ОказаниеУслуги.Дата КАК Дата,

   |    ОказаниеУслуги.Номер КАК Номер,

   |    ОказаниеУслуги.Склад,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Склад),

   |    ОказаниеУслуги.Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Мастер),

   |    ОказаниеУслуги.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Клиент)

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

После части описания запроса в нашем примере следует часть упорядочивания результатов:

   |УПОРЯДОЧИТЬ ПО

   |    Дата,

   |    Номер";

Предложение УПОРЯДОЧИТЬ ПО позволяет сортировать строки в результате запроса. После этого ключевого предложения располагается выражение упорядочивания, которое, в общем случае, представляет собой перечисление полей (выражений) и порядка вывода. В нашем случае упорядочивание будет выполняться сначала По полю выборки, обращение к которому выполняется через псевдоним – "Код", а затем по полю – "Номер". В обоих случаях порядок сортировки будет по возрастанию, который является Порядком сортировки по-умолчанию. [169]



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

Процедура РеестрДокументовОказаниеУслуги(ТабДок) Экспорт

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ(РеестрДокументовОказаниеУслуги)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Макет = ОтчетОбъект.ПолучитьМакет("РеестрДокументовОказаниеУслуги");

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ОказаниеУслуги.Дата КАК Дата,

   |    ОказаниеУслуги.Номер КАК Номер,

   |    ОказаниеУслуги.Склад,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Склад),

   |    ОказаниеУслуги.Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Мастер),

   |    ОказаниеУслуги.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ОказаниеУслуги.Клиент)

   |ИЗ

   |    Документ.ОказаниеУслуги КАК ОказаниеУслуги

   |

   |УПОРЯДОЧИТЬ ПО

   |    Дата,

   |    Номер";

   Результат = Запрос.Выполнить();

   ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");

   ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");

   ОбластьШапкаТаблицы = Макет.ПолучитьОбласть("ШапкаТаблицы");

   ОбластьПодвалТаблицы = Макет.ПолучитьОбласть("ПодвалТаблицы");

   ОбластьДетальныхЗаписей = Макет.ПолучитьОбласть("Детали");

   ТабДок.Очистить();

   ТабДок.Вывести(ОбластьЗаголовок);

   ТабДок.Вывести(ОбластьШапкаТаблицы);

   ТабДок.НачатьАвтогруппировкуСтрок();

   ВыборкаДетали = Результат.Выбрать();



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

       ОбластьДетальныхЗаписей.Параметры.Заполнить(ВыборкаДетали);

       ТабДок.Вывести(ОбластьДетальныхЗаписей, ВыборкаДетали.Уровень());

   КонецЦикла;

   ТабДок.ЗакончитьАвтогруппировкуСтрок();

   ТабДок.Вывести(ОбластьПодвалТаблицы);

   ТабДок.Вывести(ОбластьПодвал);

   //}}КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ

КонецПроцедуры

В форме отчета расположен элемент управления ПолеТабличногоДокумента с именем "ТабДок", который заполняется данными на основе макета, сформированного конструктором. [170]

В начале процедуры мы получаем макет отчета, из которого затем получаем существующие в нем области в соответствующие переменные:

...

   ОбластьЗаголовок = Макет.ПолучитьОбласть("Заголовок");

   ОбластьПодвал = Макет.ПолучитьОбласть("Подвал");

   ОбластьШапкаТаблицы = Макет.ПолучитьОбласть("ШапкаТаблицы");

   ОбластьПодвалТаблицы = Макет.ПолучитьОбласть("ПодвалТаблицы");

   ОбластьДетальныхЗаписей = Макет.ПолучитьОбласть("Детали");

...

Затем мы очищаем табличный документ и выводим в него те области, которые не содержат данных, получаемых из результата запроса:

...

   ТабДок.Очистить();

   ТабДок.Вывести(ОбластьЗаголовок);

   ТабДок.Вывести(ОбластьШапкаТаблицы);

   ТабДок.НачатьАвтогруппировкуСтрок();

...

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

После этого мы получаем выборку из результата запроса, которую перебираем в цикле:



   Результат = Запрос.Выполнить();

...

   ВыборкаДетали = Результат.Выбрать();

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

       ОбластьДетальныхЗаписей.Параметры.Заполнить(ВыборкаДетали);

       ТабДок.Вывести(ОбластьДетальныхЗаписей, ВыборкаДетали.Уровень());

   КонецЦикла; [171]

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

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

...

   ТабДок.ЗакончитьАвтогруппировкуСтрок();

   ТабДок.Вывести(ОбластьПодвалТаблицы);

   ТабДок.Вывести(ОбластьПодвал);

...

Теперь запустим 1С:Предприятие в режиме отладки и посмотрим на результат работы нашего отчета:



Таким образом на примере этого отчета мы продемонстрировали, как использовать конструктор выходной формы и познакомились с некоторыми основными конструкциями языка запросов. [172]


Отчет Рейтинг услуг


Отчет "Рейтинг услуг" будет содержать информацию о том, выполнение каких услуг принесло OOO "На все руки мастер" наибольшую прибыль в указанном периоде. На примере отчета "Рейтинг услуг" мы проиллюстрируем, как отбирать данные в некотором периоде, как задавать параметры запроса и как использовать в запросе данные из нескольких таблиц и включать в результат запроса все данные одного из источников.

Создадим новый объект конфигурации Отчет "РейтингУслуг". Перейдем на закладку "Макеты" и вызовем конструктор выходной формы.

Выберем объектную (ссылочную) таблицу справочника "Номенклатура" и виртуальную таблицу регистра накопления "Продажи.Обороты". Для того чтобы исключить неоднозначность имен в запросе, переименуем таблицу "Номенклатура" в "СпрНоменклатура" (контекстное меню правой кнопки мыши).

Затем установим курсор на таблицу "ПродажиОбороты" и вызовем диалог ввода параметров виртуальной таблицы:

Откроем диалог ввода параметров виртуальной таблицы


[173]

Укажем, что начало и конец периода будут переданы в соответствующих параметрах "ДатаНачала" и "ДатаОкончания" (символ "&" перед именем указывает, что это параметр запроса):

Затем выберем из таблиц поля "СпрНоменклатура.Ссылка" и "ПродажиОбороты.ВыручкаОборот":

Перейдем на закладку "Связи" и увидим, что конструктор уже создал связь между двумя выбранными таблицами – значение изменения регистра "Номенклатура" должно быть равно ссылке на элемент справочника "Номенклатура". [174]

Единственное, что нам останется сделать, это сбросить флаг "Все" у таблицы регистра и установить его у таблицы справочника.

Будем выбирать все элементы из справочника "Номенклатура"

Установка флага "Все" у таблицы справочника будет означать, что из справочника будут выбраны все элементы и этим элементам будет поставлено в соответствие значение оборота выручки из регистра. Таким образом, в результате запроса будут присутствовать все услуги, и для некоторых из них будут указаны обороты выручки. Для тех услуг, которые не оказывались в выбранном периоде, не будет указано ничего.




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

Вторым условием должно быть то, что выбранный элемент является услугой (это – "Простое условие"):



[175]

В дальнейшем, перед выполнением запроса, мы передадим в параметр "ВидНоменклатуры" – соответствующее значение перечисления.

Перейдем на закладку "Объединения/Псевдонимы" и укажем, что представление элемента справочника будет иметь псевдоним "Услуга", а поле регистра будет иметь псевдоним "Выручка":



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

На закладке "Итоги" определим, что нужно выводить общие итоги, и они должны представлять собой сумму значений поля "Выручка":



На закладке "Отчет" сбросим флаг "Использовать построитель отчета".

Теперь перейдем на закладку "Выходная форма". Укажем, что параметры "ДатаОкончания" и "ДатаНачала" будут редактироваться в форме в полях ввода с типом "Дата". Для параметра "ВидНоменклатуры" мы наоборот снимем признак редактирования в форме:



Нажмем "ОК". Платформа сформирует макет и форму отчета Откроем модуль формы и найдем в нем процедуру "РейтингУслуг". [176]

В этой процедуре, в той части, где выполняется установка параметров запроса, определим значение параметра "ВидНоменклатуры" (исправления выделены жирным шрифтом):

   "ВЫБРАТЬ

   |    СпрНоменклатура.Ссылка КАК Услуга,

   |    СпрНоменклатура.Представление,

   |    ПродажиОбороты.ВыручкаОборот КАК Выручка



   |ИЗ

   |    Справочник.Номенклатура КАК СпрНоменклатура

   |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , ) КАК ПродажиОбороты

   |        ПО ПродажиОбороты.Номенклатура = СпрНоменклатура.Ссылка

   |ГДЕ

   |    СпрНоменклатура.ЭтоГруппа = ЛОЖЬ

   |    И СпрНоменклатура.ВидНоменклатуры = &ВидНоменклатуры

   |

   |УПОРЯДОЧИТЬ ПО

   |    Выручка УБЫВ

   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ";

   Запрос.УстановитьПараметр("ВидНоменклатуры", Перечисления.ВидыНоменклатуры.Услуга);

   Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);

   Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

Теперь рассмотрим текст запроса, сформированный конструктором:

   Запрос.Текст =

   "ВЫБРАТЬ

   |    СпрНоменклатура.Ссылка КАК Услуга,

   |    СпрНоменклатура.Представление,

   |    ПродажиОбороты.ВыручкаОборот КАК Выручка

   |ИЗ

   |    Справочник.Номенклатура КАК СпрНоменклатура

   |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&ДатаНачала, [177] &ДатаОкончания, , ) КАК ПродажиОбороты

   |        ПО ПродажиОбороты.Номенклатура = СпрНоменклатура.Ссылка

   |ГДЕ

   |    СпрНоменклатура.ЭтоГруппа = ЛОЖЬ

   |    И СпрНоменклатура.ВидНоменклатуры = &ВидНоменклатуры

   |

   |УПОРЯДОЧИТЬ ПО



   |    Выручка УБЫВ

   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ";

Сначала, как обычно, идет часть описания запроса и в ней есть новые для нас конструкции.

При описании источников запроса (после ключевого слова ИЗ), использована возможность определения нескольких источников запроса:

   |ИЗ

   |    Справочник.Номенклатура КАК СпрНоменклатура

   |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , ) КАК ПродажиОбороты

   |        ПО ПродажиОбороты.Номенклатура = СпрНоменклатура.Ссылка

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

ЛЕВОЕ СОЕДИНЕНИЕ означает, что в результат запроса надо включить комбинации записей из обоих источников, которые соответствуют указанному после ключевого слова ПО условию. Кроме этого, в результат запроса надо включить еще и записи из первого (указанного слева от слова СОЕДИНЕНИЕ) источника, для которых не найдено соответствующих условию записей из второго источника. [178]

В описании первого источника и условия соединения нет для нас ничего нового, а вот при описании второго источника, используется возможность задания параметров виртуальной таблицы запроса:

   |    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , )

Первым параметром передается начало периода расчета итогов, вторым – конец периода. В результате исходная таблица будет содержать только обороты, рассчитанные в переданном периоде. Здесь всегда следует помнить, что если мы передаем в качестве этих параметров дату (а в нашем случае так и будет), то дата содержит и время с точностью до секунды.



Если заранее известно, что пользователя не будут интересовать результаты работы отчета в периодах, указанных с точностью до секунд, то следует учесть следующую особенность: по умолчанию время в дате установлено в 00:00:00. Поэтому, если не предпринять специальных мер, получится, что когда пользователь задаст период отчета с 01.03.2004 по 31.03.2004, итоги регистра будут рассчитаны с начала дня 01.03.2004 00:00:00 по начало дня 31.03.2004 00:00:00. Таким образом, данные за 31 число, отличные от начала дня, в расчет не войдут, что сильно удивит пользователя.

Для того чтобы исключить эту ситуацию, следует сделать две вещи.

Во-первых, в форме отчета ограничить пользователя в возможностях ввода даты начала и даты окончания, установив для соответствующих полей ввода состав даты как "Дата":

Определим состав даты...



[179]

Во-вторых, при передаче параметров использовать встроенную функцию КонецДня(). Для этого вернемся в модуль формы отчета и внесем необходимые изменения (добавления выделены жирным шрифтом):

Процедура ДействияФормыРейтингУслугСформировать(Кнопка)

   //{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА(РейтингУслуг)

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   ТабДок = ЭлементыФормы.ПолеТабличногоДокумента;

   РейтингУслуг(ТабДок, Неопределено, ДатаНачала, КонецДня(ДатаОкончания));

   //}}КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА

КонецПроцедуры

Продолжим рассматривать текст запроса. В части описания запроса есть еще одна новая для нас конструкция – задание условий отбора данных из исходных таблиц:

   "ВЫБРАТЬ

   |    СпрНоменклатура.Ссылка КАК Услуга,

   |    СпрНоменклатура.Представление,

   |    ПродажиОбороты.ВыручкаОборот КАК Выручка

   |ИЗ



   |    Справочник.Номенклатура КАК СпрНоменклатура

   |        ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, , ) КАК ПродажиОбороты

   |        ПО ПродажиОбороты.Номенклатура = СпрНоменклатура.Ссылка

   |ГДЕ

   |    СпрНоменклатура.ЭтоГруппа = ЛОЖЬ

   |    И СпрНоменклатура.ВидНоменклатуры = &ВидНоменклатуры

Условию отбора всегда предшествует ключевое слово ГДЕ. После него описывается само условие. Обратите внимание, что поля исходных таблиц, на которые накладывается условие, могут и не входить в список выборки (как в нашем случае). Кроме того, в нашем условии использован параметр запроса "ВидНоменклатуры". [180]

Далее в запросе следует часть упорядочивания результатов, а за ней – новая для нас часть, которая не встречалась ранее – описание итогов:

   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ";

Она всегда начинается с ключевого слова ИТОГИ, за которым следует описание того, какие итоги будут присутствовать в результате запроса. Сразу после слова ИТОГИ описываются агрегатные функции, которые необходимо рассчитывать в итогах. В нашем случае будет рассчитываться сумма по полю "Выручка". Затем следует ключевое слово ПО, после которого описываются группировки, в которых должны быть рассчитаны итоги. В нашем случае они отсутствуют, и используется только ключевое слово ОБЩИЕ, которое указывает на то, что итоги будут рассчитаны по всей таблице в целом.

Теперь, когда мы закончили знакомиться с текстом запроса, запустим 1С:Предприятие в режиме отладки и посмотрим, как работает наш отчет.

Зададим период отчета с 01.03.2004 по 30.04.2004. Результат будет выглядеть следующим образом:



[181]

Теперь изменим дату окончания на 31.03.2004 и убедимся, что данные за 31 марта попадают в отчет:



Таким образом, на примере этого отчета мы продемонстрировали, как отбирать данные в некотором периоде, как задавать параметры запроса и как использовать в запросе данные из нескольких таблиц и включать в результат запроса все данные одного из источников. [182]


Отчет РейтингКлиентов


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

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



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


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

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

Создадим новый объект конфигурации Отчет с именем "Универсальный2".

На закладке "Данные" создадим реквизит отчета с именем "ПостроительОтчета" и типом ПостроительОтчета. На закладке "Формы" с помощью конструктора создадим основную форму отчета и приступим к ее редактированию.

Расположим в форме две надписи с именами "Поля" и "Порядок" и заголовками "Поля:" и "Порядок:" соответственно:



Под каждым текстовым полем расположим командную панель и табличное поле с именами "КоманднаяПанельПоля" и [216] "ТабличноеПолеПоля" (соответственно "КоманднаяПанельПорядок" и "ТабличноеПолеПорядок"):



Теперь для табличного поля "ТабличноеПолеПоля" зададим источник данных как ОтчетОбъект.ПостроительОтчета.ВыбранныеПоля:



[217]

После этого для командной панели "КоманднаяПанельПоля" установим флаг "Автозаполнение" и в качестве источника действий укажем ТабличноеПолеПоля:



Затем аналогичные действия произведем для другой командной панели и табличного поля.[218]

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



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



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



Узнай больше!

О связи элементов управления и данных, можно прочитать в разделе "Данные и элементы управления" на странице 502. [219]

Теперь в нижней части формы разместим еще одну надпись с именем "Оформление" и заголовком "Оформление:", а под ним поле выбора с именем "ПолеВыбораОформление":



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

Процедура КнопкаСформироватьНажатие(Элемент)

КонецПроцедуры

ПостроительОтчета.Текст =

"ВЫБРАТЬ

|   Продажи.Номенклатура КАК Номенклатура,

|   Продажи.Клиент КАК Клиент,

|   Продажи.Мастер КАК Мастер,

|   Продажи.Количество КАК Количество,

|   Продажи.Выручка КАК Выручка,

|   Продажи.Стоимость КАК Стоимость

|

|{ВЫБРАТЬ

|      Номенклатура.*,

|      Клиент.*,

|      Мастер.*,

|      Количество.*,

|      Выручка.*,

|      Стоимость.*}

|ИЗ [220]

|   РегистрНакопления.Продажи КАК Продажи

|

|{УПОРЯДОЧИТЬ ПО Номенклатура.*, Клиент.*, Мастер.*}

|

|ИТОГИ СУММА(Количество), Сумма(Выручка), Сумма(Стоимость)

|      ПО ОБЩИЕ";

Как видите это совсем простой запрос по регистру накопления "Продажи", в котором расположены управляющие конструкции для построителя отчета.

Конструкция ВЫБРАТЬ позволяет предоставить пользователю возможность выбирать в качестве полей запроса как сами исходные поля запроса, так и все поля "через точку" от данных полей.



Конструкция УПОРЯДОЧИТЬ ПО предоставляет пользователю возможность упорядочивать строки результата запроса.

Теперь, для того, чтобы привести состав полей в "исходное" состояние, добавим команду очистки выбранных полей построителя отчета, и затем в обработчик "КнопкаСформироватьНажатие" вставим команды выполнения построителя отчета:

Процедура КнопкаСформироватьНажатие(Элемент)

   ПостроительОтчета.МакетОформления = ПолучитьМакетОформления(ПолеВыбораОформление);

   ПостроительОтчета.ОформитьМакет();

   ПостроительОтчета.Выполнить();

   ПостроительОтчета.Вывести();

КонецПроцедуры

ПостроительОтчета.Текст =

"ВЫБРАТЬ

|   Продажи.Номенклатура КАК Номенклатура,

|   Продажи.Клиент КАК Клиент,

|   Продажи.Мастер КАК Мастер,

|   Продажи.Количество КАК Количество,

|   Продажи.Выручка КАК Выручка,

|   Продажи.Стоимость КАК Стоимость

|

|{ВЫБРАТЬ

|      Номенклатура.*,

|      Клиент.*,

|      Мастер.*,

|      Количество.*,

|      Выручка.*, [221]

|      Стоимость.*}

|ИЗ

|   РегистрНакопления.Продажи КАК Продажи

|

|{УПОРЯДОЧИТЬ ПО Номенклатура.*, Клиент.*, Мастер.*}

|

|ИТОГИ СУММА(Количество), Сумма(Выручка), Сумма(Стоимость)

|      ПО ОБЩИЕ";

ПостроительОтчета.ВыбранныеПоля.Очистить();

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



Здесь следует сказать о том, что построитель отчета предоставляет возможность полностью формировать макет будущего отчета, как задавая его целиком (свойство "Макет"), так и путем описания отдельных областей макета (свойства "МакетДетальныхЗаписей", "МакетЗаголовка" и т.д.).

Теперь, для завершения нашего универсального отчета следует заполнить список выбора поля выбора "ПолеВыбораОформление" и установить начальное значение поля:

Процедура КнопкаСформироватьНажатие(Элемент)

   ПостроительОтчета.МакетОформления = ПолучитьМакетОформления(ПолеВыбораОформление);

   ПостроительОтчета.ОформитьМакет();

   ПостроительОтчета.Выполнить();

   ПостроительОтчета.Вывести();

КонецПроцедуры

ПостроительОтчета.Текст =

"ВЫБРАТЬ

|   Продажи.Номенклатура КАК Номенклатура,

|   Продажи.Клиент КАК Клиент, [222]

|   Продажи.Мастер КАК Мастер,

|   Продажи.Количество КАК Количество,

|   Продажи.Выручка КАК Выручка,

|   Продажи.Стоимость КАК Стоимость

|

|{ВЫБРАТЬ

|      Номенклатура.*,

|      Клиент.*,

|      Мастер.*,

|      Количество.*,

|      Выручка.*,

|      Стоимость.*}

|ИЗ

|   РегистрНакопления.Продажи КАК Продажи

|

|{УПОРЯДОЧИТЬ ПО Номенклатура.*, Клиент.*, Мастер.*}

|

|ИТОГИ СУММА(Количество), Сумма(Выручка), Сумма(Стоимость)

|      ПО ОБЩИЕ";

ПостроительОтчета.ВыбранныеПоля.Очистить();

СписокВыбора = ЭлементыФормы.ПолеВыбораОформление.СписокВыбора;

СписокВыбора.Добавить(СтандартноеОформление.БезОформления, "БезОформления");

СписокВыбора.Добавить(СтандартноеОформление.Апельсин, "Апельсин");

СписокВыбора.Добавить(СтандартноеОформление.Асфальт, "Асфальт");

СписокВыбора.Добавить(СтандартноеОформление.Бирюза, "Бирюза");



СписокВыбора.Добавить(СтандартноеОформление.Текстиль, "Текстиль");

ПолеВыбораОформление = СтандартноеОформление.БезОформления;

Запустим 1С:Предприятие в режиме отладки и откроем отчет "Универсальный2".

Выберем поля: "Мастер", "Номенклатура.ВидНоменклатуры", "Номенклатура" и "Выручка". Зададим следующий порядок сортировки:

·"Мастер" по возрастанию,
·"Номенклатура.ВидНоменклатуры" по убыванию,
·"Номенклатура" по возрастанию.[223]
Выберем оформление "Апельсин" и нажмем "Сформировать" Результат будет выглядеть следующим образом:



Теперь изменим условия формирования отчета. Выберем поля "Клиент", "Номенклатура" и "Выручка", порядок сортировки будет по возрастанию значения поля "Клиент", а вариант оформления – "Асфальт":



Таким образом, на примере этого отчета вы познакомились с возможностью задания условий для построителя отчета и одним из способов формирования макета отчета на основе интерактивного выбора пользователя.[224]


Отчет УниверсальныйЗ


Посмотрим, как будет выглядеть отчет "Универсальный2", если, при создании его в конструкторе выходной формы, указать использование построителя отчета.

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

На закладке "Итоги" отметим получение общих итогов и перейдем на закладку "Отчет":

Используя построитель отчета можно вывести результат запроса в табличный документ, сводную таблицу, диаграмму или сводную Диаграмму. Для каждого выбранного представления данных можно настроить параметры оформления (например, выбрать вариант стандартного оформления) и некоторые способы формирования [225] представления (например, для табличного документа – размещение группировок, реквизитов, итогов, наличие группировок и т.д.).

В нашем случае согласимся с параметрами, которые конструктор предложил по умолчанию, единственное, что мы изменим – вариант стандартного оформления установим "Асфальт".

Перейдем на закладку "Выходная форма" и откроем закладку: "Форма настройки построителя отчета":

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

В нашем случае мы снова согласимся с тем, что конструктор предлагает по умолчанию, и нажмем "ОК".

Конструктор сформирует форму отчета и форму настроек отчета.[226]

Запустим 1С:Предприятие в режиме отладки, откроем отчет "УниверсальныйЗ", нажмем кнопку "Настройка..." и установим параметры выбранных полей и порядка такими же, как в последнем примере с отчетом "Универсальный2":

·выбранные поля: "Клиент", "Номенклатура", "ВыручкаОборот",
·порядок: "Клиент" по возрастанию:

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



Отчет ВыручкаМастеров


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

Создадим новый объект конфигурации Отчет "ВыручкаМастеров". Перейдем на закладку "Макет" и запустим конструктор выходной формы.

Выберем виртуальную таблицу регистра накопления "Продажи.Обороты". Зададим для нее значения параметров "НачалоПериода", "КонецПериода" и "Периодичность":

[183]

После этого выберем из таблицы следующие поля:

·"ПродажиОбороты.Мастер",
·"ПродажиОбороты.Период",
·"ПродажиОбороты.Клиент",
·"ПродажиОбороты.ВыручкаОборот":

Теперь перейдем на закладку "Объединения/Псевдонимы" и зададим псевдоним "Выручка" для поля "ПродажиОбороты.ВыручкаОборот":

На закладке "Порядок" определим, что результат запроса будет отсортирован по возрастанию значения поля "Период" и на закладке "Итоги" зададим получение общих итогов и промежуточных итогов по полям "Мастер" и "Период":


[184]

На закладке "Отчет" сбросим флаг "Использовать построитель отчета".

На закладке "Выходная форма" отметим, что тип параметров "ДатаНачала" и "ДатаОкончания" будет Дата. Нажмем "OK".

Сразу, как и в предыдущем отчете, определим состав даты для полей ввода, расположенных в форме, и затем в вызове процедуры "ВыручкаМастеров" уточним передачу последнего параметра при помощи функции КонецДня():

Процедура ДействияФормыВыручкаМастеровСформировать(Кнопка)

//{{КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА(ВыручкаМастеров)




   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   ТабДок = ЭлементыФормы.ПолеТабличногоДокумента;

   ВыручкаМастеров(ТабДок, ДатаНачала, КонецДня(ДатаОкончания));

   //}}КОНСТРУКТОР_ВЫХОДНЫХ_ФОРМ_ПРОЦЕДУРА_ВЫЗОВА

КонецПроцедуры

Теперь обратимся к процедуре "ВыручкаМастеров" и в первую очередь рассмотрим текст запроса, сформированный конструктором:

Запрос.Текст =

   "ВЫБРАТЬ

   |    ПродажиОбороты.Мастер КАК Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ПродажиОбороты.Мастер),

   |    ПродажиОбороты.Период КАК Период,

   |    ПродажиОбороты.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ПродажиОбороты.Клиент),

   |    ПродажиОбороты.ВыручкаОборот КАК Выручка

   |ИЗ

   |    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, День, ) КАК ПродажиОбороты

   |

   |УПОРЯДОЧИТЬ ПО

   |    Период

   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ,

   |    Мастер,

   |    Период"; [185]

В части описания запроса обратите внимание, что у источника данных кроме задания начала и окончания периода расчета итогов задана периодичность выбираемых данных – "День":

   |ИЗ

   |    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, День, ) КАК ПродажиОбороты

Именно благодаря этому у нас появляется возможность описать среди выбранных полей поле "Период".

Далее в тексте запроса следует известная нам часть упорядочивания результатов, и в следующей части – описание итогов – мы видим новые для нас строки:



   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ,

   |    Мастер,

   |    Период";

Помимо общих итогов, в нашем запросе будут рассчитаны промежуточные итоги по полям "Мастер" и "Период". [186]

Теперь, чтобы наглядно продемонстрировать смысл наших дальнейших действий, запустите 1С:Предприятие в режиме отладки и посмотрите на результат работы отчета "Выручка мастеров" за период c 01.03.2004 по 30.04.2004:



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

Вернемся к модулю отчета "ВыручкаМастеров" и в части описания итогов запроса уточним, каким образом должны рассчитываться итоги по полю "Период":

Запрос.Текст =

   "ВЫБРАТЬ

   |    ПродажиОбороты.Мастер КАК Мастер,

   |    ПРЕДСТАВЛЕНИЕ(ПродажиОбороты.Мастер),

   |    ПродажиОбороты.Период КАК Период,

   |    ПродажиОбороты.Клиент,

   |    ПРЕДСТАВЛЕНИЕ(ПродажиОбороты.Клиент),

   |    ПродажиОбороты.ВыручкаОборот КАК Выручка [187]

   |ИЗ

   |    РегистрНакопления.Продажи.Обороты(&ДатаНачала, &ДатаОкончания, День, ) КАК ПродажиОбороты

   |

   |УПОРЯДОЧИТЬ ПО

   |    Период

   |ИТОГИ

   |    СУММА(Выручка)

   |ПО

   |    ОБЩИЕ,

   |    Мастер,



   |    Период ПЕРИОДАМИ(День, &ДатаНачала, &ДатаОкончания)";

Такая запись говорит о том, что итоги должны быть, рассчитаны периодами равными дню, в интервале дат, задаваемом параметрами "ДатаНачала" и "ДатаОкончания".

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

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

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

       ОбластьМастер.Параметры.Заполнить(ВыборкаМастер);

       ТабДок.Вывести(ОбластьМастер, ВыборкаМастер.Уровень());

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

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

           ОбластьПериод.Параметры.Заполнить(ВыборкаПериод);

           ТабДок.Вывести(ОбластьПериод, ВыборкаПериод.Уровень());

           ВыборкаДетали = ВыборкаПериод.Выбрать();

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

               ОбластьДетальныхЗаписей.Параметры.Заполнить(ВыборкаДетали);

               ТабДок.Вывести(ОбластьДетальныхЗаписей, ВыборкаДетали.Уровень());

           КонецЦикла;

       КонецЦикла;

   КонецЦикла; [188]

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



Запустим 1С:Предприятие в режиме отладки и выполним отчет "ВыручкаМастеров" за период с 20.03.2004 по 20.04.2004.

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

Очевидно, что такой внешний вид отчета абсолютно "нечитабелен", поэтому мы снова вернемся к модулю отчета и внесем небольшие уточнения в алгоритм вывода областей табличного документа:

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

       ОбластьМастер.Параметры.Заполнить(ВыборкаМастер);

       ТабДок.Вывести(ОбластьМастер, ВыборкаМастер.Уровень());

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

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

           ОбластьПериод.Параметры.Заполнить(ВыборкаПериод);

           ТабДок.Вывести(ОбластьПериод, ВыборкаПериод.Уровень(),,Ложь);

           ВыборкаДетали = ВыборкаПериод.Выбрать();

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

               ОбластьДетальныхЗаписей.Параметры.Заполнить(ВыборкаДетали);

               ТабДок.Вывести(ОбластьДетальныхЗаписей, ВыборкаДетали.Уровень());

           КонецЦикла;

       КонецЦикла;

   КонецЦикла;

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

Запустим 1С:Предприятие в режиме отладки и снова выполним отчет "ВыручкаМастеров" за период с 20.03.2004 по 20.04.2004. [189]

На этот раз результат выглядит гораздо лучше:



Итак, на примере этого отчета мы продемонстрировали, как строить многоуровневые группировки в запросе, как обходить все даты в выбранном периоде и как управлять состоянием группировок в табличном документе. [190]


мы создадим отчет, аналогичный отчету


Tеперь, мы создадим отчет, аналогичный отчету "ВыручкаМастеров", который будет выводить результат в сводную диаграмму.

Откроем конфигуратор и создадим новый объект [227] конфигурации Отчет с именем "ВыручкаМастеров2". Запустим конструктор выходной формы, и выберем поля "Мастер", "Клиент", "ВыручкаОборот" и "СтоимостьОборот" из виртуальной таблицы регистра накопления "Продажи". На закладке "Итоги" укажем группировочные поля "Мастер" и "Клиент" и суммируемые поля "ВыручкаОборот" и "СтоимостьОборот". В заключение на закладке "Отчет" отметим, что результат должен быть выведен только в сводную диаграмму. Нажмем "ОК".

Запустим 1С:Предприятие в режиме отладки и откроем отчет "ВыручкаМастеров2". На закладке "Настройка" укажем, что порядок вывода должен быть по убыванию значения поля "ВыручкаОборот". После этого двойным щелчком мыши по полю сводной диаграммы откроем окно выбора параметров диаграммы и в точки диаграммы поместим поле "Мастер", в серии – поле "Клиент", а поля "ВыручкаОборот" и "СтоимостьОборот" поместим в данные:

[228]

Теперь зададим условие отбора таким, что значение поля "Номенклатура.ВидНоменклатуры" должно быть равно "Материал" (для ввода условия отбора можно использовать клавишу "Insert"). И в заключение, после того как диаграмма будет заново сформирована, раскроем группу серий "ВыручкаОборот":



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

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


Отображение существующего результата анализа


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

Построитель = Новый ПостроительОтчетаАнализаДанных;

Построитель.Вывести(РезультатАнализа, ТабличныйДокумент); [473]



Поиск ассоциаций


Тип анализа АнализДанныхПоискАссоциаций предназначен для поиска часто встречаемых вместе групп объектов или значений характеристик, а также выполняет поиск правил ассоциаций. Этот тип анализа может использоваться для определения часто приобретаемых вместе товаров или услуг.

Типы колонок источника данных:

·Не используется – колонка не используется в анализе.
·Объект – колонка содержит объект, например документ "Оказание услуги".
·Элемент – колонка содержит элемент, например номенклатуру из документа "Оказание услуги".

Параметры:

·МинимальныйПроцентСлучаев – (Число) – минимальный процент случаев, в которых наблюдается группа элементов. Найденные группы, у которых процент случаев меньше, в отчет включены не будут.
·МинимальнаяДостоверность – (Число) – минимальная достоверность правила. Найденные правила, у которых достоверность меньше, в отчет включены не будут.
·МинимальнаяЗначимость – (Число) – минимальная значимость правила. Найденные правила, значимость которых меньше, в отчет включены не будут. Значимость правила – величина, характеризующая насколько правило важно. Чем выше значимость, тем интересней правило.
·ПоискПоИерархии – (Булево) – необходимость поиска по иерархии. При помощи этого параметра можно указать анализу, что необходимо искать ассоциации не только среди элементов, но и среди групп.
·ТипОтсеченияПравил – (избыточные, покрытые) – тип отсечения найденных правил. Избыточные – отсекать избыточные правила, покрытые – отсекать правила, покрытые другими правилами.
·ТипИсточникаДанных – (объектный, событийный) – тип источника данных. Анализ работает с двумя типами источника. Объектный – каждая строка источника содержит [455] объект с его характеристиками. Событийный – источник данных содержит список событий. Например, состав документа "Оказание услуги".
·ИспользованиеЧисловыхЗначений – (как булево, как число) как интерпретировать числовые значения. Можно интерпретировать числовые значения как числа или как логические значения, т.е. рассматривать ноль как Ложь, а все остальные ненулевые значения как Истина.
·ИгнорироватьНезаполненныеЗначения – (Булево) – Как использовать незаполненные значения. Т.е. игнорировать их или нет.
·Порядок – (по достоверности, по значимости, по количеству случаев) – определяет порядок отображения данных в результате анализа. [456]



Поиск последовательностей


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

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

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

Типы колонок источника данных:

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

Параметры:

·МинимальныйПроцентСлучаев – (Число) – минимальное число последовательностей, в которых должен наблюдаться шаблон последовательности.
·ПоискПоИерархии – (Булево) – необходимо ли осуществлять поиск по иерархии.
·МинимальныйИнтервал – (Булево) – признак того, что установлен минимальный интервал между наблюдаемыми событиями. Установка минимального интервала означает, что для того, чтобы элементы попали в искомую последовательность необходимо, чтобы временной интервал между элементами был не менее установленного.
·ЕдиницаМинимальногоИнтервала – единица минимального интервала [459]
·КратностьМинимальногоИнтервала – (Число) – кратность минимального интервала
·МаксимальныйИнтервал – (Булево) – признак того, что установлен максимальный интервал между наблюдаемыми событиями. Установка максимального интервала означает, что для того, чтобы элементы попали в искомую последовательность необходимо, чтобы временной интервал между элементами был не более установленного.
·ЕдиницаМаксимальногоИнтервала – единица максимального интервала
·КратностьМаксимальногоИнтервала – (Число) – кратность максимального интервала
·ИнтервалЭквивалентностиВремени – (Булево) – признак того, что установлен интервал эквивалентности времени между наблюдаемыми событиями. Если установлен интервал эквивалентности времени, то события, временной интервал между которыми меньше интервала эквивалентности времени считаются произошедшими в одно время.
·ЕдиницаИнтервалаЭквивалентностиВремени – единица интервала эквивалентности времени
·КратностьИнтервалаЭквивалентностиВремени – (Число) – кратность интервала эквивалентности времени
·МинимальнаяДлина – (Число) – минимальная длина последовательности.
·Порядок – (по длине, по количеству случаев) – определяет порядок отображения данных в результате анализа. [460]



Постановка задачи


Задача, которую мы перед собой поставим, будет заключаться в следующем: мы создадим механизм, который позволит пользователю произвольным образом описывать материалы, и, что самое главное, вести учет в разрезе всех тех описаний, которые могут быть заданы пользователем.

Описывать материалы пользователь сможет следующим образом: для каждого материала будет возможность создать некоторые (произвольные) характеристики этого материала (например, цвет, производитель и пр.). Затем, при поступлении материалов можно будет задать конкретные значения интересующих характеристик (например, при поступлении электрических кабелей можно будет указать, что они белого цвета и их сечение равно 2,5 мм2, а при поступлении резиновых шлангов указать, что они черного цвета и произведены на фирме "Fagumit, Sp. z o.o.").

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

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

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



Постановка задачи


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

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

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

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

Для исключения подобных ситуаций в каждой базе к номерам документов и кодам справочников мы будем добавлять уникальный префикс, однозначно идентифицирующий базу данных. Тогда, даже [400] если номера новых документов в двух базах совпадут, они все равно будут отличаться префиксом и конфликта не возникнет.

Для хранения префикса номеров мы используем объект конфигурации, с которым до сих пор еще не работали – это объект Константа.



Постановка задачи


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

Для реализации такой схемы работы распределенная информационная база подойдет как нельзя лучше, и сначала мы организуем обмен с отделениями, используя исключительно интерактивные средства.[428]



Построитель отчета


Построитель отчета является объектом встроенного языка, позволяющим выполнять различные настройки запроса в ходе выполнения программы. Такие настройки могут выполняться как средствами встроенного языка (в результате работы некоторого алгоритма работы программы), так и интерактивно, непосредственно пользователем. [208]

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

Формировать параметры настройки на основании текста запроса построитель отчета может двумя способами: автоматически и на основании указаний, расположенных в тексте запроса. [209]

Автоматически параметры настройки формируются вызовом метода ЗаполнитьНастройки() следующим образом:

·полями, доступными для выбора в качестве полей отбора, порядка или вывода в отчет (свойство "ДоступныеПоля"), становятся все поля из списка выборки и все их подчиненные поля,
·в список полей, выбранных для вывода в отчет (свойство "ВыбранныеПоля"), добавляются все поля из списка выборки,
·полями, доступными для выбора в качестве измерений, становятся все поля из предложения ИТОГИ ПО и все их подчиненные поля,
·в список измерений по строкам (свойство "ИзмеренияСтроки") добавляются все поля из предложения ИТОГИ ПО,
·в доступные отборы (свойство "Отбор") добавляются параметры виртуальных таблиц.

Вторым способом формирования параметров настроек построителя отчета является выделение их в тексте запроса. Для этого используются фигурные скобки "{ }". Фигурными скобками могут быть выделены параметры построителя отчета, а также некоторые синтаксические конструкции:

·ВЫБРАТЬ ... – описывает поля, которые пользователь сможет выбирать для вывода,
·ГДЕ ... – описывает поля, на которые пользователь может накладывать ограничения,
·УПОРЯДОЧИТЬ ПО ... – описывает поля для обозначения порядка,
·ИТОГИ ПО ... – описывает поля, по которым будут выводиться итоговые значения.

После того, как значения параметров указаны, вывод результатов запроса возможен как принудительно (используя методы Выполнить() и Вывести()), либо автоматически, если результаты выводятся в сводную таблицу. [210]



Построитель отчета анализа данных


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

Объект ПостроительОтчетаАнализаДанных может использоваться для выполнения трех различных задач.

Во-первых, для визуального отображения существующего результата анализа данных.

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

В-третьих, для настройки параметров модели прогноза.



В качестве примера общестатистического анализа


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


[453]
Результат анализа будет выглядеть следующим образом:


[454]

В качестве примера возьмем данные


В качестве примера возьмем данные регистра "Продажи": поле "Регистратор" и измерение "Номенклатура":


[457]
Результат анализа будет выглядеть следующим образом:


[458]

В качестве примера снова возьмем


В качестве примера снова возьмем данные регистра "Продажи": измерения "Номенклатура", "Контрагент" и поле "Период":


[461]
Результат анализа будет выглядеть следующим образом:


На этот раз мы проанализируем


На этот раз мы проанализируем данные справочника "Контрагенты". В качестве входных колонок мы используем поля реквизитов справочника "КоличествоРозничныхТочек", "КоличествоАвтомобилей", "ВремяРаботыОрганизации" и "ВремяЗаключенияДоговора". Прогнозируемой колонкой будет поле реквизита справочника "Контрагенты" – "ПрекращениеОтношений".


[464]
Результат анализа будет иметь следующий вид:


[465]

Для анализа мы возьмем те


Для анализа мы возьмем те же поля справочника "Контрагенты", что и в предыдущем примере: "КоличествоРозничныхТочек", "КоличествоАвтомобилей", [467] "ВремяРаботыОрганизации", "ВремяЗаключенияДоговора" и "ПрекращениеОтношений". В качестве ключа мы используем значение поля "Ссылка":

Результат анализа будет выглядеть следующим образом:


[468][469]

В качестве примера мы рассмотрим


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


[471]
Тогда менеджер, нажав на кнопку "Предложение", может открыть список товаров, которые, с большой долей вероятности, также имеет смысл предложить этому клиенту:


[472]

Пример интерактивного обмена в распределенной информационной базе


Для построения распределенной информационной базы нам понадобится создать еще один объект конфигурации План обмена, который мы назовем "Отделения".

Для этого плана обмена мы установим свойство "Распределенная информационная база":

Установим свойство "Распределенная информационная база"...

Перейдем на закладку "Прочее" и определим тот же состав данных для обмена, что и в плане обмена "Филиалы": отметим все объекты конфигурации, относящиеся к подсистеме "УчетУслугИМатериалов".

Запустим 1С:Предприятие в режиме отладки. [429]

Откроем план обмена "Отделения" и зададим параметры центрального узла (предопределенный элемент плана обмена): код "ЦБ" и наименование "Центральная база". После этого создадим новый узел с кодом "Отд" и наименованием "Отделение". Обратите внимание, что для созданного нами узла стали доступны три иконки в командной панели формы плана обмена: "Создать начальный образ" "Записать изменения" и "Прочитать изменения":


Стали доступны команды работы с распределенной информационной базой...

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


[430]

На следующем шаге укажем каталог информационной базы и нажмем "Готово". Система создаст в указанном каталоге начальный образ информационной базы нашего отделения.

Запустим 1С:Предприятие, подключим новую базу нашего отделения и откроем ее в конфигураторе. Обратите внимание на то, что конфигурация нашего отделения стала защищена от изменений средствами управления распределенной информационной базой:

Конфигурация подчиненного узла защищена от изменений средствами управления распределенной информационной базой...






[431]
Запустим базу отделения в режиме отладки и откроем план обмена "Отделения":

Обратите внимание, что в базе подчиненного узла сам подчиненный узел является предопределенным узлом плана обмена, а узел центральной базы отмечен красной пиктограммой, указывающей на то, что он является главным для информационной базы отделения. Кроме этого для узла центральной базы доступны только команды "Записать изменения" и "Прочитать изменения".
Теперь проверим работу обмена данными. Откроем список констант и зададим значение константы "ПрефиксНумерации" – "ОТ".
После этого откроем справочник клиентов и добавим в него нового клиента. Затем выполним запись изменений для центральной базы (указав каталог обмена).
Перейдем в центральную базу и выполним чтение изменений в центральной базе. Убедимся, что новый клиент, созданный в базе отделения, присутствует и в центральной базе.
Теперь посмотрим, как будут переноситься изменения конфигурации между главным и подчиненным узлом. В конфигураторе центральной базы создадим новую константу с именем "НоваяКонстанта". Выполним обновление конфигурации базы данных и запустим 1С:Предприятие в режиме отладки. Откроем план обмена "Отделения" и выполним запись изменений для подчиненного узла. [432]
После этого закроем конфигуратор информационной базы отделения, и выполним чтение изменений в базе подчиненного узла. По окончании чтения система выдаст следующее сообщение:

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

Программный обмен в распределенной информационной базе


Все описанные выше действия по обмену данными в распределенной информационной базе можно выполнить программно.

Мы создадим обработку, которая будет программно выполнять для выбранного узла все те действия, которые были рассмотрены в предыдущем разделе. [433]

Для этого в конфигураторе центральной базы создадим новый объект конфигурации Обработка с именем "ОбменСОтделениями". Создадим основную форму обработки и расположим на ней поле ввода с именем "ПолеВводаОтделение", подписью "Отделение:" и типом ПланОбменаСсылка.Отделения:

После этого расположим в форме три кнопки: "Создать начальный образ" с именем "КнопкаСоздатьНачальныйОбраз", "Записать изменения" с именем "КнопкаЗаписатьИзменения" и "Прочитать изменения" с именем "КнопкаПрочитатьИзменения":


[434]

Начнем с создания обработчика нажатия кнопки "Создать начальный образ". Текст обработчика будет выглядеть следующим образом:

Процедура КнопкаСоздатьНачальныйОбразНажатие(Элемент)

Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.ВыборКаталога);

   Диалог.Заголовок = "Укажите каталог информационной базы:";

   Если Диалог.Выбрать() Тогда

       ПланыОбмена.СоздатьНачальныйОбраз(ПолеВводаОтделение,"File=" + Диалог.Каталог);

       Предупреждение("Создание начального образа узла завершено.");

   КонецЕсли;

КонецПроцедуры

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




Теперь создадим обработчик нажатия кнопки "Записать изменения":

Процедура КнопкаЗаписатьИзмененияНажатие(Элемент)

   Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Сохранение);

   Диалог.Заголовок = "Укажите файл обмена:";

   Если Диалог.Выбрать() Тогда

       // Создать и проинициализаровать объект ЗаписьХМL

       ЗаписьXML = Новый ЗаписьXML;

       ЗаписьXML.ОткрытьФайл(Диалог.ПолноеИмяФайла);

       // Создать объект ЗаписьСообщенияОбмена и начать запись сообщения

       ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();

       ЗаписьСообщения.НачатьЗапись(ЗаписьXML, ПолеВводаОтделение);

       // Записать содержимое тела сообщения обмена данными распределенной ИБ

       ПланыОбмена.ЗаписатьИзменения(ЗаписьСообщения);

       // Закончить запись сообщения и запись ХМL

       ЗаписьСообщения.ЗакончитьЗапись(); [435]

       ЗаписьXML.Закрыть();

       Предупреждение("Запись изменений завершена.");

   КонецЕсли;

КонецПроцедуры

В начале процедуры мы вызываем диалог ввода имени файла, в который будут записаны изменения. После этого мы создаем объект ЗаписьXML для работы с этим файлом. Затем создаем объект ЗаписьСообщенияОбмена, с помощью которого будем создавать сообщение обмена. В методе НачатьЗапись(), во втором параметре, мы указываем, для какого узла обмена будет создаваться это сообщение. После этого мы выполняем метод ЗаписатьИзменения() объекта ПланыОбменаМенеджер, который и записывает изменения, предназначенные для передачи в выбранный узел, в указанное сообщение обмена. В заключение мы как обычно заканчиваем запись сообщения обмена и закрываем файл.





Узнай больше!

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

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

Учитывая все вышесказанное, идеальным вариантом является выполнение обмена данными в монопольном режиме. Однако такой вариант не всегда приемлем, в силу специфики организации работы конкретных информационных баз. [436]

И последним мы создадим обработчик нажатия кнопки "Прочитать изменения":

Процедура КнопкаПрочитатьИзмененияНажатие(Элемент)

   Диалог = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);

   Диалог.Заголовок = "Укажите файл обмена:";

   Если Диалог.Выбрать() Тогда

       // Создать и проинициализаровать объект ЧтениеХМL

       ЧтениеXML = Новый ЧтениеXML;

       ЧтениеXML.ОткрытьФайл(Диалог.ПолноеИмяФайла);

       // Создать объект ЧтениеСообшенияОбмена и начать чтение сообщения

       ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

       ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

       // Прочитать содержимое тела сообщения



       ПланыОбмена.ПрочитатьИзменения(ЧтениеСообщения);

       // Закончить чтение сообщения и чтение XML

       ЧтениеСообщения.ЗакончитьЧтение();

       ЧтениеXML.Закрыть();

       Предупреждение("Чтение изменений завершено.");

   КонецЕсли;

КонецПроцедуры

В начале процедуры мы снова вызываем диалог ввода имени файла, который будет прочитан, и создаем объект ЧтениеXML для работы с этим файлом. Затем создаем объект ЧтениеСообщенияОбмена для чтения сообщения, содержащегося в указанном файле. Затем методом ПрочитатьИзменения() объекта ПланыОбменаМенеджер мы читаем полученное сообщение. В заключение процедуры мы завершаем чтение сообщения обмена и закрываем файл.

Проверить работу нашей обработки можно на примере, аналогичном приведенному в разделе универсального обмена Данными.[437]

Следует лишь сделать несколько заключительных замечаний.

При использовании механизма распределенных информационных баз становятся доступными четыре события объект ПланОбменаОбъект, которые позволяют управлять отправкой \ приемом данных на уровне отдельных элементов данных:

·ПриОтправкеДанныхГлавному(),
·ПриОтправкеДанныхПодчиненному(),
·ПриПолученииДанныхОтГлавного(),
·ПриПолученииДанныхОтПодчиненного().
Эти события будут вызываться для каждого элемента данных включаемого в сообщение. Работу этих событий можно увидеть добавив в модуль объекта План обмена следующий текст:

Процедура ПриОтправкеДанныхГлавному(ЭлементДанных, ОтправкаЭлемента)

   Сообщить("ПриОтправкеДанныхГлавному "+ЭлементДанных);

КонецПроцедуры

Процедура ПриОтправкеДанныхПодчиненному(ЭлементДанных, ОтправкаЭлемента)

   Сообщить("ПриОтправкеДанныхПодчиненному "+ЭлементДанных);

КонецПроцедуры

Процедура ПриПолученииДанныхОтГлавного(ЭлементДанных, ПолучениеЭлемента, ОтправкаНазад)



   Сообщить("ПриПолученииДанныхОтГлавного "+ЭлементДанных);

КонецПроцедуры

Процедура ПриПолученииДанныхОтПодчиненного(ЭлементДанных, ПолучениеЭлемента, ОтправкаНазад)

   Сообщить("ПриПолученииДанныхОтПодчиненного "+ЭлементДанных);

КонецПроцедуры

В первом параметре всех перечисленных событий находится тот элемент данных, для которого вызвано это событие. [438]

Параметр "ОтправкаЭлемента" позволяет управлять тем, какая информация будет помещена в сообщение. Он может принимать три значения:

·ОтправкаЭлементаДанных.Авто – значение по умолчанию – указывает на то, что элемент данных будет помещен в сообщение,
·ОтправкаЭлементаДанных.Удалить – в сообщение будет помещено значение, предназначенное для удаления этого элемента данных,
·ОтправкаЭлементаДанных.Игнорировать – в сообщение не будет помещено ничего, связанного с этим элементом данных.
Параметр "ПолучениеЭемента" позволяет указать, будет ли прочитанный элемент данных записан в базу данных, или нет. Параметр также может принимать три значения:

·ПолучениеЭлементаДанных.Авто – значение по умолчанию. Если элемент данных получен от главного узла – он будет записан всегда. Если элемент данных получен от подчиненного узла, он будет записан только в случае, если не зарегистрированы изменения для этого элемента данных,
·ПолучениеЭлементаДанных.Принять – полученный элемент данных будет записан всегда,
·ПолучениеЭлементаДанных.Игнорировать – проигнорировать получение элемента данных и ничего не записывать.
Также в событиях получения данных существует третий параметр – "ОтправкаНазад", имеющий тип Булево. Этот параметр позволяет выполнять принудительную регистрацию изменений для полученного элемента данных в базе-получателе. Такая необходимость может возникнуть, например, в случае, когда при приеме данных от узла-отправителя обнаружено, что полученные данные противоречивы (например, в узле-отправителе была допущена ошибка при изменении Данных). Тогда мы можем проигнорировать присланные изменения и, подняв флаг "ОтправкаНазад", вызвать принудительную регистрацию изменений полученного элемента данных в нашей базе для узла-отправителя. В результате последующего обмена состояние этого [439] элемента данных в узле-отправителе будет установлено таким же, как и в нашей базе.



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

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

Рассмотрим несколько вариантов реконфигурирования структуры узлов распределенной информационной базы.

Допустим, необходимо переместить один из подчиненных узлов в корень дерева:



Для этого следует выполнить следующие действия:

// В информационной базе Узла2

ПланыОбменаМенеджер.УстановитьГлавныйУзел(Неопределено); [440]

// В информационной базе Узла1

ПланыОбменаМенеджер.УстановитьГлавныйУзел(Узел2);

При этом будут удалены все записи регистрации изменений конфигурации Узла1, относящиеся к Узлу2, т.к. передача изменений конфигурации будет возможна теперь только от Узла2 к Узлу1. Записи регистрации изменения данных удалены не будут, т.к. передача изменений данных будет по-прежнему возможна между этими узлами.

Таким же образом, используя значение параметра метода Неопределено, мы можем отключать от дерева отдельную информационную базу или целое поддерево:



// В информационной базе Узла1

ПланыОбменаМенеджер.УстановитьГлавныйУзел(Неопределено); [441]

Кроме этого мы можем создавать распределенную информационную базу из отдельных информационных баз с идентичной конфигурацией:



// В информационных базах Узла2, Узла3 и Узла4

ПланыОбменаМенеджер.УстановитьГлавныйУзел(Узел1); [442]

Проверка работы обмена данными


Прежде всего создадим новый каталог, в котором будет размещаться база нашего филиала и сохраним в этот каталог нашу конфигурацию (Конфигурация

Сохранить конфигурацию в файл...). Запустим 1С:Предприятие в режиме отладки и установим необходимые значения в нашей центральной базе. Прежде всего зададим значение константы "ПрефиксНомеров" – "ЦБ":

После этого откроем план обмена "Филиалы" и зададим параметры узла по умолчанию – т.е. параметры нашей базы. Код базы будет "ЦБ", а наименование – "Центральная база".

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


[422]

Затем создадим новый узел, который будет соответствовать базе филиала, присвоим ему код "Фил" и наименование "Филиал":

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

Таким образом, в результате обмена данными центральная база сформировала файл обмена, содержащий изменения всех данных, которыми она обменивается с филиалом.

Настало время перейти к базе филиала. Запустим систему в режиме Конфигуратора и добавим в список баз новую базу с пустой конфигурацией, которая будет расположена в созданном нами каталоге базы филиала. В конфигураторе откроем конфигурацию и загрузим конфигурацию из файла (Конфигурация

Загрузить конфигурацию из файла...). Запустим 1С:Предприятие в режиме отладки. [423]

Первым делом зададим значение константы "ПрефиксНомеров" "ФЛ":

Затем откроем план обмена "Филиал" и опишем предопределенный узел (узел текущей информационной базы) кодом "Фил" и наименованием "Филиал":

После этого создадим новый узел плана обмена с кодом "ЦБ", наименованием "Центральная база" и признаком "Главный":


[424]

Теперь, для большей наглядности откроем список справочника "Клиенты". Сейчас в этом справочнике нет ни одного элемента. Запустим обработку "ОбменДанными" и нажмем "Выполнить".

Справочник будет заполнен элементами, а в окне сообщений появится текст:

Теперь проверим, как будет происходить обмен в другую сторону. Создадим в справочнике "Клиенты" нового клиента с произвольным наименованием. После этого снова нажмем "Выполнить" в открытой форме обработки "ОбменДанными". Затем перейдем в центральную базу, также выполним обмен данными и убедимся, что клиент, созданный в базе филиала перенесен в центральную базу.[425]



Работа с запросами


Для работы с запросами используется объект встроенного языка Запрос. Он позволяет получать информацию, хранящуюся в полях базы данных, в виде выборки, сформированной по заданным правилам.



Реорганизация справочника Номенклатура


Откроем конфигуратор и создадим сначала новый объект конфигурации Перечисление с именем "ВидыНоменклатуры".

На закладке "Данные" добавим два значения перечисления: "Материал" и "Услуга":

Затем добавим в справочник "Номенклатура" новый реквизит "ВидНоменклатуры" с типом ПеречислениеСсылка.ВидыНоменклатуры:


[132]

После этого запустим 1С:Предприятие в режиме отладки и зададим для каждого элемента справочника "Номенклатура" соответствующее значение реквизита "ВидНоменклатуры":

Теперь посмотрим, как можно использовать новые данные, полученные благодаря использованию перечисления "ВидыНоменклатуры". [133]



Создание документа НачисленияСотрудникам


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

Откроем конфигуратор и создадим новый документ. Назовем его "НачисленияСотрудникам".

Этот документ будет иметь табличную часть "Начисления", содержащую следующие реквизиты:

·"Сотрудник", тип СправочникСсылка.Сотрудники,
·"ГрафикРаботы", тип СправочникСсылка.ВидыГрафиковРаботы,
·"ДатаНачала", тип Дата,
·"ДатаОкончания", тип Дата,
·"ВидРасчета", тип ПланВидовРасчетаСсылка.ОсновныеНачисления,
·"Результат", тип Число, длина 15, точность 2.

Реквизиты "ДатаНачала" и "ДатаОкончания" понадобятся нам для того, чтобы задавать период, в котором должна действовать запись расчета.

На закладке "Движения" запретим оперативное проведение документа, отметим, что документ будет создавать движения по регистру расчета "Начисления" и запустим конструктор движений.[341]

В окне конструктора выберем табличную часть "Начисления" нажмем "ЗаполнитьВыражения". Для реквизитов "ПериодДействияКонец" и "БазовыйПериодКонец" укажем выражение "КонецДня(ТекСтрокаНачисления.ДатаОкончания)"

Реквизиту "ИсходныеДанные" поставим в соответствие реквизит табличной части "Результат", а для реквизита "Результат" наоборот удалим выражение, присвоенное ему конструктором:

Нажмем "ОК" и посмотрим текст обработчика, созданный конструктором:

Процедура ОбработкаПроведения(Отказ, Режим)

   //{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Для Каждого ТекСтрокаНачисления Из Начисления Цикл

       // регистр Начисления

       Движение = Движения.Начисления.Добавить();[342]




       Движение.Сторно = Ложь;

       Движение.ВидРасчета = ТекСтрокаНачисления.ВидРасчета;

       Движение.ПериодДействияНачало = ТекСтрокаНачисления.ДатаНачала;

       Движение.ПериодДействияКонец = КонецДня(ТекСтрокаНачисления.ДатаОкончания);

       Движение.ПериодРегистрации = ТекСтрокаНачисления.ДатаНачала;

       Движение.БазовыйПериодНачало = ТекСтрокаНачисления.ДатаНачала;

       Движение.БазовыйПериодКонец = КонецДня(ТекСтрокаНачисления.ДатаОкончания);

       Движение.Сотрудник = ТекСтрокаНачисления.Сотрудник;

       Движение.ГрафикРаботы = ТекСтрокаНачисления.ГрафикРаботы;

       Движение.ИсходныеДанные = ТекСтрокаНачисления.Результат;

   КонецЦикла;

   // записываем движения регистров

   Движения.Начисления.Записать();

   //}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

КонецПроцедуры

Запустим 1С:Предприятие в режиме отладки и посмотрим, как работает наш документ.

Начислим оклад за март всем сотрудникам OOO "На все руки мастер", как показано на рисунке:



[343]

Проведем документ и посмотрим, какие движения он сформировал в регистре "Начисления":





Обратите внимание на то, что платформа привела период регистрации каждой записи к началу периода регистра расчета (в обработчике проведения мы указывали значение даты документа – 08.04.2004). Кроме этого заметьте, что с каждой записью мы сохранили в реквизите "ИсходныеДанные" размер оклада сотрудника, введенный в документе, чтобы в дальнейшем рассчитать сумму оплаты по окладу.

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


Создание движений документа ПриходнаяНакладная


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

Откроем конфигуратор. В окне редактирования объекта конфигурации Документ "ПриходнаяНакладная", на закладке "Движения" запустим конструктор движений документа.

В список регистров добавим регистр "РегистрБухгалтерии.Управленческий". В качестве источника данных выберем табличную часть документа "ПриходнаяНакладная" – "Материалы". Счет дебета установим равным "ПланыСчетов.Основной.Товары" (41), а счет кредита – "ПланыСчетов.Основной.РасчетыСПоставщиками" (60).[303]

Нажмем кнопку "Заполнить выражения". У вас должен получиться следующий результат:

Нажмем "ОК" и посмотрим, какой текст платформа добавила в обработчик проведения документа "ПриходнаяНакладная":

Процедура ОбработкаПроведения(Отказ, Режим)

//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Для Каждого ТекСтрокаМатериалы Из Материалы Цикл

       // регистр ОстаткиМатериалов Приход

       Движение = Движения.ОстаткиМатериалов.Добавить();

       Движение.ВидДвижения = ВидДвиженияНакопления.Приход;

       Движение.Период = Дата;

       Движение.Материал = ТекСтрокаМатериалы.Материал;

       Движение.НаборСвойств = ТекСтрокаМатериалы.НаборСвойств;

       Движение.Склад = Склад;

       Движение.Количество = ТекСтрокаМатериалы.Количество;

   КонецЦикла;

   Для Каждого ТекСтрокаМатериалы Из Материалы Цикл




       // регистр СтоимостьМатериалов Приход

       Движение = Движения.СтоимостьМатериалов.Добавить();

       Движение.ВидДвижения = ВидДвиженияНакопления.Приход; [304]

       Движение.Период = Дата;

       Движение.Материал = ТекСтрокаМатериалы.Материал;

       Движение.Стоимость = ТекСтрокаМатериалы.Сумма;

   КонецЦикла;

   Для Каждого ТекСтрокаМатериалы Из Материалы Цикл

       // регистр Управленческий

       Движение = Движения.Управленческий.Добавить();

       Движение.СчетДт = ПланыСчетов.Основной.Товары;

       Движение.СчетКт = ПланыСчетов.Основной.РасчетыСПоставщиками;

       Движение.Период = Дата;

       Движение.Сумма = ТекСтрокаМатериалы.Сумма;

       Движение.КоличествоДт = ТекСтрокаМатериалы.Количество;

       Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Материалы] = ТекСтрокаМатериалы.Материал;

   КонецЦикла;

   // записываем движения регистров

   Движения.ОстаткиМатериалов.Записать();

   Движения.СтоимостьМатериалов.Записать();

   Движения.Управленческий.Записать();

   //}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

КонецПроцедуры

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

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



Другими словами, запись:

Движение.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.Материалы]

равносильна записи:

Движение.СубконтоДт.Материалы

Запустим 1С:Предприятие в режиме отладки, откроем документ ПриходнаяНакладная №1 и перепроведем его.

Посмотрим, какие движения сформировал документ в регистре бухгалтерии Управленческий:



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

После этого перепроведем документ ПриходнаяНакладная №2 и посмотрим, какие движения сформирует он.

Теперь перейдем к более сложной задаче – добавлению движений по регистру "Управленческий" в документ "ОказаниеУслуги".[306]


Создание функции РозничнаяЦена()


Сначала мы создадим функцию, которая будет возвращать нам актуальную розничную цену номенклатуры. Откроем конфигуратор, в ветке Общие

Общие модули создадим новый объект конфигурации Модуль и назовем его "РаботаСоСправочниками".

Разместим в нем следующий текст:

Функция РозничнаяЦена(АктуальнаяДата, ЭлементНоменклатуры) Экспорт

//создать вспомогательный объект Отбор

Отбор = Новый Структура("Номенклатура", ЭлементНоменклатуры);

//получить актуальные значения ресурсов регистра

ЗначенияРесурсов = РегистрыСведений.Цены.ПолучитьПоследнее(АктуальнаяДата, Отбор);

Возврат ЗначенияРесурсов.Цена;

КонецФункции // РозничнаяЦена(...)

Для получения розничной цены мы будем передавать в функцию два параметра:

·АктуальнаяДата – параметр типа Дата, который будет определять точку на оси времени, на которую нас интересует значение розничной цены
·ЭлементНоменклатуры – ссылка на элемент справочника "Номенклатура", для которого мы хотим получить розничную цену.

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

Во второй строке мы обращаемся к менеджеру регистра сведений "Цены" (РегистрыСведений.Цены) и выполняем метод ПолучитьПоследнее(), который возвращает нам значения ресурсов наиболее поздней записи регистра, которая соответствует передаваемой дате ("АктуальнаяДата") и значениям измерений регистра ("Отбор"). [124]

Значения ресурсов возвращаются в структуре, поэтому в следующей строке мы получаем искомую нами розничную цену просто указав имя нужного нам ресурса регистра через точку (ЗначенияРесурсов.Цена).

Теперь проверим, как работает эта функция. [125]



Создание интерфейсов


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

Скорее всего, это будут руководители, мастера и бухгалтеры. В соответствии с этим мы создадим три различных интерфейса: "Руководитель", "Мастер" и "Бухгалтер". Кроме этого, следует не забыть про то, что у каждой базы данных, как правило, есть администратор – специально выделенный человек, отвечающий за непрерывное функционирование базы, сохранность и достоверность данных. Поскольку администратору нужно предоставить возможность осуществлять обслуживание базы данных – для него мы тоже создадим отдельный интерфейс – "Администратор". [384]

Создадим новый объект конфигурации Интерфейс, и на экране появится конструктор главного меню:

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

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

Мы так и поступим. Первый интерфейс, который мы будем создавать, будет интерфейс "Бухгалтер". Поэтому выберем подсистемы "Бухгалтерия" и "РасчетЗарплаты" и нажмем "Установить". В конструкторе меню обновится список команд и используемых подменю. Нажмем "Построить" и зададим имя интерфейса – "Бухгалтер". Укажем, что этот интерфейс будет относиться к подсистемам "Бухгалтерия" и "РасчетЗарплаты". [385]




Вместе с палитрой свойств на экране открылось окно редактор-интерфейса – остановимся на нем подробнее:



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

Сейчас наш интерфейс "Бухгалтер" содержит только панель главного меню, пункты которого отображены в редакторе панели. При нажатии на любой пункт меню открывается список подменю, содержащий команды этого пункта. [386]

В данном случае нас все устраивает, за исключением пункта "Прочие", в котором для команды "Основной" мы дадим более понятный текст – "План счетов Основной":

Изменим текст пункта подменю



Теперь создадим интерфейс "Мастер". Для него в конструкторе меню выберем подсистему "УчетУслугИМатериалов" и укажем, что сам он тоже будет относиться к подсистеме "УчетУслугИМатериалов".

Два последних интерфейса, будем создавать следующим образом: меню интерфейса "Руководитель" построим по всем трем подсистемам конфигурации:



А при создании меню интерфейса "Администратор" мы не будем выбирать никаких подсистем, а сразу построим меню. Обратите внимание, что в этом случае система включила в меню пункты стандартных действий: "Файл", "Операции", "Сервис", "Окна" и [387] "Справка". Скорее всего, они понадобятся будущему администратору информационной базы.

Разработчик, по своему усмотрению может добавлять, изменять и удалять пункты меню. Эти действия просты и не требуют специальных описаний. И поскольку создание удобного и эргономичного меню – задача творческая – мы лишь показали возможность быстрого создания некоей заготовки, которую разработчик может впоследствии самостоятельно доработать под нужды конкретной группы пользователей. [388]


Создание константы ПрефиксНомеров


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

Теперь приступим к созданию константы, в которой мы будем хранить значение префикса номеров. Откроем конфигуратор и создадим новый объект конфигурации Константа с именем "ПрефиксНумерации". Определим тип значения константы – Строка, с фиксированной длиной 2 символа.



Создание наборов свойств


Откроем элемент справочника "Номенклатура" – "Кабель электрический". Перейдем на закладку "Свойства" и создадим набор свойств этого элемента под названием "Белый".[273]

Он будет состоять их следующих характеристик:

·"Цвет" – "Белый",
·"Сечение, мм2" – 2,5:

Затем создадим набор свойств для элемента справочника "Номенклатура" – "Шланг резиновый". [274]

Этот набор свойств будет называться "Польша" и состоять из следующих характеристик:

"Цвет" – "Черный",

"Производитель" – "Fagumit":

Теперь откроем документ ПриходнаяНакладная №2 и укажем, что был закуплен белый электрический кабель в количестве 2 шт. и польский резиновый шланг.[275]

Затем скопируем первую строку документа и укажем, что был закуплен еще и черный электрический кабель в количестве 3 шт. (в процессе ввода нам придется создать еще один набор свойств для электрического кабеля – "Черный", у которого "Цвет" – "Черный" и "Сечение" – 2,5):

Проведем документ и посмотрим на движения документа по регистру "ОстаткиМатериалов":


[276]

Кроме этого посмотрим на записи, которые содержатся в регистре сведений "ЗначенияСвойствНоменклатуры":



Создание новых объектов конфигурации


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

Затем создадим еще один объект конфигурации Справочник с именем "ДополнительныеСвойстваНоменклатуры".

После этого создадим объект конфигурации План видов характеристик с именем "СвойстваНоменклатуры". Тип значения характеристик установим следующим:

·Число, длина 15, точность 3,
·Строка, длина 25,
·Дата,
·Булево,
·СправочникСсылка.ДополнительныеСвойстваНоменклатуры:


[259]

Справочнику "ДополнительныеСвойстваНоменклатуры" укажем владельца – план видов характеристик "СвойстваНоменклатуры" После этого определим, что дополнительные значения характеристик плана видов характеристик будут располагаться в справочнике "ДополнительныеСвойстваНоменклатуры":

В заключение создадим объект конфигурации Регистр сведений с именем "ЗначенияСвойствНоменклатуры".

Измерения регистра:

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

"ВидСвойства", тип ПланВидовХарактеристикСсылка.СвойстваНоменклатуры.

Ресурс регистра:

"Значение", тип Характеристика.СвойстваНоменклатуры

Обратите внимание, что мы имеем возможность определить тип значения ресурса регистра, как Характеристика.<имя>. По сути, это [260] определение представляет собой составной тип данных, как он задан в типе значения соответствующего плана видов характеристик. To есть ресурс регистра может иметь значение любого типа из тех, которые описаны в типе значения плана видов характеристик.



Создание объекта конфигурации План счетов Основной


Приступим к созданию плана счетов OOO "На все руки мастер". Как мы говорили в начале этой главы, бухгалтерский учет в нашем OOO сильно упрощен и план счетов, по которому работает бухгалтерия, содержит всего три счета: "Товары", "Капитал" и "Дебиторская задолженность".

Откроем конфигуратор и создадим новый объект конфигурации План счетов. Присвоим ему имя – "Основной".

На закладке "Данные" создадим признак учета "Количественный". Перейдем на закладку "Субконто" и укажем, что субконто для этого плана счетов будут находиться в плане видов характеристик "ВидыСубконто". Максимальное количество субконто на счете установим равным двум. Также создадим признак учета субконто "Количественный" и сразу откроем закладку "Прочее".[296]

Нажмем кнопку "Предопределенные" и создадим четыре предопределенных счета:

·"Товары", код 41, активный, с количественным учетом в разрезе материалов:


[297]

·"Расчеты с поставщиками", код 60, активно/пассивный:

·"Дебиторская задолженность", код 62, активно/пассивный, с учетом в разрезе клиентов:


[298]

·"Капитал", с кодом 90, активно/пассивный:

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

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

Узнай больше!

Для плана счетов можно установить свойство "Автопорядок no коду". Это свойство используется для того, чтобы указать системе, что упорядочивание no полю Порядок должно всегда подставляться в [299] тех случаях, когда пользователь или разработчик выбирает упорядочивание no коду. Его нужно использовать, прежде всего, тогда, когда с точки зрения пользователя нужно упорядочивать план счетов по коду с учетом разделителей кода счета. Например, если счета "10.11" и "10.2" упорядочивать no коду счета, то счета будут располагаться так:

"10.11"

"10.2"

Это правильно с точки зрения сортировки строк, но не соответствует логическому смыслу кодов.

Но если заданы значения поля Порядок "10.11" и "10. 2" и установлено свойство "Автопорядок no коду", то при выборе упорядочивания no коду пользователь будет, фактически, получать порядок, учитывающий разделители:

"10.2"

"10.11".

Если свойство не устанавливать, то нужно будет в явном виде выбирать упорядочивание no полю Порядок.[300]



Создание объекта конфигурации План видов характеристик ВидыСубконто


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

Откроем конфигуратор и создадим новый объект конфигурации План видов характеристик. Зададим его имя – "ВидыСубконто".

Поскольку нам понадобится некий вспомогательный справочник, в котором пользователи будут осуществлять "свободное творчество" по созданию значений новых объектов аналитического учета – создадим объект конфигурации Справочник и назовем его "Субконто". [291]

Затем на закладке "Владельцы" укажем, что этот справочник подчинен плану видов характеристик "ВидыСубконто":

Закроем окно редактирования справочника и вернемся к нашему плану видов характеристик.[292]

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

·СправочникСсылка.Клиенты,
·СправочникСсылка.Номенклатура,
·СправочникСсылка.Субконто:

Бухгалтерия нашего OOO "На все руки мастер" ведет учет движения денежных средств только в разрезе материалов и клиентов, но не исключено, что в дальнейшем понадобится дополнительная аналитика (поэтому мы используем и справочник "Субконто"). Обратите внимание, что тот справочник, который будет использован в качестве дополнительных значений характеристик, тоже должен входить в составной тип данных типа значений характеристик, иначе конфигуратор выдаст сообщение об ошибке.

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

После этого перейдем на закладку "Прочее" и начнем ввод предопределенных значений плана видов характеристик.

Создадим предопределенный вид субконто: "Материалы", с кодом "00001" и типом СправочникСсылка.Номенклатура, и затем [293] создадим вид субконто: "Клиенты", с кодом "00002" и типом СправочникСсылка.Клиенты:

На этом создание видов субконто завершено, и мы можем перейти к знакомству со следующим объектом конфигурации, который будет использован нами, – планом счетов. [294]



Создание оборотного регистра накопления Продажи


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

Регистры накопления могут быть регистрами остатков и регистрами оборотов.

Существующие в нашей учебной конфигурации регистры "ОстаткиМатериалов" и "СтоимостьМатериалов" являются регистрами остатков. Если вы вспомните момент, когда мы создавали отчет "Материалы", то в конструкторе отчета мы видели, что для таких регистров система создает три виртуальные таблицы: таблица остатков, оборотов и совокупная таблица остатков и оборотов.

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

В остальном оборотный регистр ни чем не отличается от регистра остатков.

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

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

Совсем иная ситуация в случае регистра накопления поддерживающего накопление остатков. Для него выбор измерений должен выполняться исходя из того, что движения регистра могут быть осуществлены "в две стороны": приход и расход. Таким образом, в качестве измерений нужно выбирать те параметры, по которым движения точно будут осуществляться как в одну, так и в другую сторону. [154]

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




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

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

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

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

Нарушение этого принципа построения регистров накопления будет вести к непроизводительному использованию ресурсов системы и, как следствие, замедлению работы и падению производительности.

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

На закладке "Данные" создадим измерения регистра:

·"Номенклатура", тип СправочникСсылка.Номенклатура,
·"Клиент", тип СправочникСсылка.Кпиенты,
·"Мастер", тип СправочникСсылка.Сотрудники. [155]
У регистра будет три ресурса:

·"Количество", тип Число, длина 15, точность 3,
·"Выручка",тип Число, длина 15, точность 2,
·"Стоимость", тип Число, длина 15, точность 2.
Откроем окно редактирования объекта конфигурации Документ "ОказаниеУслуги" и на закладке "Движения" укажем, что этот документ будет создавать движения по регистру "Продажи".

Запустим 1С:Предприятие в режиме отладки и откроем формы списка регистров накопления "Продажи" и "ОстаткиМатериалов". Обратите внимание, что формы практически одинаковы, за исключением состава измерений и ресурсов. [156]


Создание обработки ОбменДанными


Откроем конфигуратор и создадим новый объект конфигурации Обработка с именем "ОбменДанными". Перейдем на закладку "Прочее" и откроем модуль объекта. Создадим в нем процедуру "ОбменСФилиалами":

Процедура ОбменСФилиалами() Экспорт

ВыборкаУзлов = ПланыОбмена.Филиалы.Выбрать();

   Пока ВыборкаУзлов.Следующий() Цикл

       // Произвести обмен данными со всеми узлами,

       // кроме текущего (ЭтотУзел)

       Если ВыборкаУзлов.Ссылка <> ПланыОбмена.Филиалы.ЭтотУзел() Тогда

           УзелОбъект = ВыборкаУзлов.ПолучитьОбъект();

           // Получить сообщение

           УзелОбъект.ПрочитатьСообщениеСИзменениями();

           // Сформировать сообщение

           УзелОбъект.ЗаписатьСообщениеСИзменениями();

       КонецЕсли;

   КонецЦикла;

КонецПроцедуры

Алгоритм работы этой процедуры заключается в следующем: В цикле мы перебираем узлы, которые содержатся в плане обмена "Филиалы", и для всех узлов, кроме себя самого, производим сначала чтение сообщений, поступивших из других узлов обмена (процедуру [406] "ПрочитатьСообщенияСИзменениями" мы создадим позднее), а затем формируем для них сообщения, предназначенные для передачи и содержащие измененные данные для этого узла (процедура "ЗаписатьСообщениеСИзменениями" также будет создана нами позднее).

Теперь создадим основную форму обработки и в обработчик события нажатия кнопки "Выполнить" – "КнопкаВыполнитьНажатие" вставим вызов процедуры ОбменСФилиалами():

Процедура КнопкаВыполнитьНажатие(Кнопка)

   ОбменСФилиалами();

КонецПроцедуры



Создание отчета ДиаграммаНачислений


Создадим новый объект конфигурации Отчет и назовем его "ДиаграммаНачислений". Создадим основную форму отчета и разместим на ней элемент управления диаграмма Ганта с именем "ДиаграммаГанта":

Откроем модуль формы отчета и в обработчик события "Нажатие" кнопки сформировать вставим заготовку запроса:

Процедура КнопкаСформироватьНажатие(Кнопка)[366]

Запрос = Новый Запрос;

   Запрос.Текст =

КонецПроцедуры

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

·"НачисленияФактическийПериодДействия.Сотрудник",
·"НачисленияФактическийПериодДействия.ВидРасчета",
·"НачисленияФактическийПериодДействия.ПериодДействияНачало",
·"НачисленияФактическийПериодДействия.ПериодДействияКонец",
·"НачисленияФактическийПериодДействия.Результат",
·"НачисленияФактическийПериодДействия.Регистратор",
·"НачисленияФактическийПериодДействия.Регистратор.Представление":

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

Процедура КнопкаСформироватьНажатие(Кнопка)

   Запрос = Новый Запрос;

   Запрос.Текст =

       "ВЫБРАТЬ

       |    НачисленияФактическийПериодДействия.Сотрудник,

       |    НачисленияФактическийПериодДействия.ВидРасчета,

       |    НачисленияФактическийПериодДействия.ПериодДействияНачало,

       |    НачисленияФактическийПериодДействия.ПериодДействияКонец,

       |    НачисленияФактическийПериодДействия.Результат,

       |    НачисленияФактическийПериодДействия.Регистратор,




       |    НачисленияФактическийПериодДействия.Регистратор.Представление [367]

       |ИЗ

       |    РегистрРасчета.Начисления.ФактическийПериодДействия КАК НачисленияФактическийПериодДействия";

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

   Диаграмма = ЭлементыФормы.ДиаграммаГанта;

   // Запретить обновление диаграммы

   Диаграмма.Обновление = Ложь;

   Диаграмма.Очистить();

   Диаграмма.ОтображатьЗаголовок = Ложь;

   //заполнить диаграмму

   Пока ВыборкаРезультата.Следующий() Цикл

       //получить серию, точку н значение для них

       ТекущаяСерия=Диаграмма.УстановитьСерию(ВыборкаРезультата.ВидРасчета,ВыборкаРезультата.ВидРасчета);

       ТекущаяТочка = Диаграмма.УстановитьТочку(ВыборкаРезультата.Сотрудник, ВыборкаРезультата.Сотрудник);

       ТекущееЗначение = Диаграмма.ПолучитьЗначение(ТекущаяТочка,ТекущаяСерия);

       // создать нужные нам интервалы в значении

       ТекущийИнтервал = ТекущееЗначение.Добавить();

       ТекущийИнтервал.Начало = ВыборкаРезультата.ПериодДействияНачало;

       ТекущийИнтервал.Конец = ВыборкаРезультата.ПериодДействияКонец;

       ТекущийИнтервал.Текст = ВыборкаРезультата.РегистраторПредставление;

       ТекущийИнтервал.Расшифровка = ВыборкаРезультата.Регистратор;

   КонецЦикла;

   //раскрасить серии своими цветами

   Для Каждого Серия Из Диаграмма.Серии Цикл

       Если Серия.Значение = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

           Серия.Цвет = WEBЦвета.Желтый;



       ИначеЕсли Серия.Значение = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

           Серия.Цвет = WEBЦвета.Зеленый;

       ИначеЕсли Серия.Значение = ПланыВидовРасчета.ОсновныеНачисления.Невыход Тогда

           Серия.Цвет = WEBЦвета.Красный;

       КонецЕсли;

   КонецЦикла;

   //разрешить обновление диаграммы

   Диаграмма.Обновление = Истина;

КонецПроцедуры [368]

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

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

После того, как точка и серия получены, с помощью метода ПолучитьЗначение() мы получаем соответствующее им значение диаграммы.

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

После того, как все значения диаграммы сформированы, мы раскрашиваем серии своими цветами. Серии диаграммы представляют собой коллекцию значений, которую мы перебираем при помощи конструкции Для каждого ... Цикл.

Запустим 1С:Предприятие в режиме отладки и посмотрим на результат работы отчета:



[369]

А теперь посмотрим, как выглядит механизм вытеснения По периоду действия "в действии". Откроем документ Начисления сотрудникам №3 и вместо одного прогула с 1 по 10 число зададим Гусакову два прогула: с 3 по 7 число и с 12 по 15 число.

Проведем документ и снова нажмем "Сформировать" в отчете:



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

Следует отметить, что существует также возможность интерактивной настройки параметров диаграммы Ганта, доступная через пункт контекстного меню "Шкала времени и масштаб...".[370]


Создание отчета НачисленияСотрудникам


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

Создадим в конфигураторе новый объект конфигурации Отчет. Назовем его "НачисленияСотрудникам".

Запустим конструктор выходной формы. Выберем следующие поля таблицы регистра расчета "Начисления":

На закладке "Объединения/Псевдонимы" определим следующие псевдонимы выбранных полей:


[357]

На закладке "Порядок" отметим автоупорядочивание. На закладке "Итоги" зададим получение общих итогов и промежуточных итогов по сотруднику:

В заключение на закладке "Отчет" сбросим флаг "Использовать построитель отчета". Нажмем "ОК" и запустим 1С:Предприятие в режиме отладки.

В результате работы отчета мы получим следующую таблицу:


[358]



Создание отчета ОборотноСальдоваяВедомость


Теперь нам осталось только создать отчет для бухгалтерии OOO "На все руки мастер" и наше знакомство с использованием регистра бухгалтерии будет закончено.

Единственный отчет, которым пользуется бухгалтерия нашего OOO – это отчет "Оборотно-сальдовая ведомость".

Для того чтобы сформировать этот отчет, откроем конфигуратор и создадим новый объект конфигурации Отчет с именем "ОборотноСальдоваяВедомость". На закладке "Макеты" откроем конструктор выходной формы, и посмотрим, что нам предлагает платформа для выбора.

Бухгалтерский отчет "Оборотно-сальдовая ведомость" представляет собой таблицу, в строках которой перечислены все [312] имеющиеся в плане счетов счета, а в колонках – начальное сальдо, оборот и конечное сальдо по дебету и кредиту каждого счета.

Поэтому нам, для построения такого отчета, понадобятся две исходные таблицы: объектная (ссылочная) таблица плана счетов "Основной" и виртуальная таблица регистра бухгалтерии "Управленческий.ОстаткиИОбороты":

Из таблицы "Основной" мы выберем поля "Код" и "Наименование", а из таблицы "УправленческийОстаткиИОбороты", мы выберем следующие поля:

·"СуммаНачальныйРазвернутыйОстатокДт",
·"СуммаНачальныйРазвернутыйОстатокКт",
·"СуммаОборотДт",
·"СуммаОборотКт",
·"СуммаКонечныйРазвернутыйОстатокДт",
·"СуммаКонечныйРазвернутыйОстатокКт":


[313]

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

Затем на закладке "Объединения/Псевдонимы" зададим псевдонимы полей регистра: "СальдоНачДт", "СальдоНачКт", "ОборотДт", "ОборотКт", "СальдоКонДт" и СальдоКонКт":

После этого на закладке "Порядок" укажем, что результат запроса должен быть отсортирован по возрастанию поля "Код", а на закладке "Итоги" укажем, что должны быть сформированы общие итоги, представляющие собой суммарные значения полей регистра:


[314]

В заключение на закладке "Отчет" сбросим флаг "Использовать построитель отчета".

Наш отчет готов. Нажмем "ОК", запустим 1С:Предприятие в режиме отладки и посмотрим, как работает наш отчет:


[315]



Создание отчета РейтингКлиентов


Создадим в конфигураторе новый объект конфигурации Отчет "РейтингКлиентов". Затем создадим основную форму отчета и расположим на ней поле выбора с именем "ПолеВыбора", подписью "Тип диаграммы:" и подсказкой "Выбор типа диаграммы" (Форма

Вставить элемент управления...):


[198]

Затем разместим под ним диаграмму с именем "Диаграмма". Изменим размеры областей диаграммы, и зададим текст области заголовка – "Рейтинг клиентов":

В модуле формы создадим процедуру "Сформировать" с заготовкой для текста запроса:

Процедура Сформировать()

   // Вставить содержимое обработчика.

   Запрос = Новый Запрос;

   Запрос.Текст =

   ;

КонецПроцедуры

Установим курсор в предпоследней строке, перед точкой с запятой и вызовем конструктор запроса (Текст

Конструктор запроса...). Выберем виртуальную таблицу регистра накопления "Продажи.Обороты" и из нее одно поле "ПродажиОбороты.Клиент.Представление".[199]

Затем добавим новое поле (иконка "Добавить" в командной панели над списком полей) и при помощи построителя выражений определим его как разность между выручкой и стоимостью:

В результате список выбранных полей будет иметь следующий вид:

На закладке "Объединения/Псевдонимы" укажем, что поле "ПродажиОбороты.Клиент.Представление" будет иметь псевдоним "Клиент", а вычисляемое поле – псевдоним "Доход":


[200]

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

Процедура Сформировать()

   // Вставить содержимое обработчика.

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ПродажиОбороты.Клиент.Представление КАК Клиент,

   |    ПродажиОбороты.ВыручкаОборот - ПродажиОбороты.СтоимостьОборот КАК Доход




   |ИЗ

   |    РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты

   |

   |УПОРЯДОЧИТЬ ПО

   |    Доход УБЫВ";

КонецПроцедуры

По сравнению с предыдущими отчетами, текст запроса довольно прост; единственным интересным местом, на которое следует обратить внимание, является поле "Доход", являющееся результатом вычисления выражения:

...

   |    ПродажиОбороты.ВыручкаОборот - ПродажиОбороты.СтоимостьОборот КАК Доход

...

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

Добавим в процедуру следующий текст (добавленный текст выделен жирным шрифтом):

Процедура Сформировать()

   // Вставить содержимое обработчика.

   Запрос = Новый Запрос;

   Запрос.Текст =

   "ВЫБРАТЬ

   |    ПродажиОбороты.Клиент.Представление КАК Клиент,

   |    ПродажиОбороты.ВыручкаОборот - ПродажиОбороты.СтоимостьОборот КАК Доход

   |ИЗ

   |    РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты [201]

   |

   |УПОРЯДОЧИТЬ ПО

   |    Доход УБЫВ";

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

   Диаграмма = ЭлементыФормы.Диаграмма;

   //запретить обновление и автотранспонирование диаграммы

   Диаграмма.Обновление = Ложь;

   Диаграмма.АвтоТранспонирование = Ложь;

   //создать единственную точку диаграммы

   ТочкаДиаграммы = Диаграмма.УстановитьТочку("Доход");

   //перебрать выборку результата запроса и создать серии

   //и значения

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

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



       Диаграмма.УстановитьЗначение(ТочкаДиаграммы,ТекущаяСерияДиаграммы,ВыборкаРезультатаЗапроса.Доход);

   КонецЦикла;

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

   Диаграмма.Обновление = Истина;

   Диаграмма.АвтоТранспонирование = Истина;

КонецПроцедуры

Сначала, с помощью метода Выполнить() мы получаем результат запроса. Затем методом Выбрать() получаем выборку записей из результата запроса в переменной "ВыборкаРезультатаЗапроса".

Перед началом заполнения мы отключаем обновление и автотранспонирование диаграммы для того, чтобы заполнение данными выполнялось быстрее.

После этого добавляем в диаграмму единственную точку.

Затем мы организуем цикл по выборке из результата запроса и в цикле добавляем серии в нашу диаграмму. Каждому клиенту будет соответствовать своя серия. После этого мы устанавливаем значение точки, передавая методу УстановитьЗначение() точку, серию, для которой устанавливается значение в этой точке, и само значение.

После заполнения диаграммы данными мы включаем свойство "Обновление", чтобы новое состояние диаграммы было отображено, и [202] "АвтоТранспонирование" для того, чтобы различные типы диаграмм, которые будут выбраны в поле выбора, отображались правильно.



Узнай больше!

Следует сделать несколько замечаний no оптимизации заполнения диаграммы данными.

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

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



Теперь создадим обработчик события формы "ПриОткрытии", и добавим в него установку значения поля выбора и типа диаграммы, и вызов нашей процедуры "Сформировать":

Процедура ПриОткрытии()

   //Заполним список поля выбора

   ПолеВыбора = ТипДиаграммы.Гистограмма;

   ЭлементыФормы.Диаграмма.ТипДиаграммы = ПолеВыбора;

   Сформировать();

КонецПроцедуры [203]

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

СписокВыбора = ЭлементыФормы.ПолеВыбора.СписокВыбора;

СписокВыбора.Добавить(ТипДиаграммы.График, "График");

СписокВыбора.Добавить(ТипДиаграммы.Гистограмма, "Гистограмма");

СписокВыбора.Добавить(ТипДиаграммы.ГистограммаОбъемная, "Гистограмма 3D");

СписокВыбора.Добавить(ТипДиаграммы.ГистограммаГоризонтальная,"Гистограмма горизонтальная");

СписокВыбора.Добавить(ТипДиаграммы.ГистограммаГоризонтальнаяОбъемная,"Гистограмма горизонтальная 3D");

СписокВыбора.Добавить(ТипДиаграммы.Круговая, "Круговая");

СписокВыбора.Добавить(ТипДиаграммы.КруговаяОбъемная,"Круговая объемная");

СписокВыбора.Добавить(ТипДиаграммы.Изометрическая,"Изометрическая");

СписокВыбора.Добавить(ТипДиаграммы.ИзометрическаяНепрерывная,"Изометрическая непрерывная");

СписокВыбора.Добавить(ТипДиаграммы.ИзометрическаяЛента, "Изометрическая лента");

СписокВыбора.Добавить(ТипДиаграммы.ИзометрическаяПирамида, "Изометрическая пирамида");

И в заключение, создадим обработчик события поля выбора "При изменении", и выполним в нем установку типа диаграммы:

Процедура ПолеВыбораПриИзменении(Элемент)

   ЭлементыФормы.Диаграмма.ТипДиаграммы = ПолеВыбора;

КонецПроцедуры [204]

Запустим 1С:Предприятие в режиме отладки и откроем отчет "РейтингКлиентов". Обратите внимание, что при наведении курсора на столбец гистограммы появляется подсказка:





Теперь изменим тип диаграммы на "Круговая объемная":



Только что мы рассмотрели с вами общий случай заполнения Диаграммы данными. Однако если исходные данные могут быть получены в виде таблицы значений или области ячеек табличного Документа, существует более простой и эффективный способ [205] заполнения диаграммы данными, используя свойство диаграммы – "ИсточникДанных".

Вернемся в модуль формы отчета "РейтингКлиентов" и все строки, которыми мы добавляли в диаграмму данные:

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

   Диаграмма = ЭлементыФормы.Диаграмма;

   //запретить обновление и автотранспонирование диаграммы

   Диаграмма.Обновление = Ложь;

   Диаграмма.АвтоТранспонирование = Ложь;

   //создать единственную точку диаграммы

   ТочкаДиаграммы = Диаграмма.УстановитьТочку("Доход");

   //перебрать выборку результата запроса и создать серии и значения

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

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

       Диаграмма.УстановитьЗначение(ТочкаДиаграммы,ТекущаяСерияДиаграммы, ВыборкаРезультатаЗапроса.Доход);

   КонецЦикла;

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

   Диаграмма.Обновление = Истина;

   Диаграмма.АвтоТранспонирование = Истина;

заменим одной строкой:

   ЭлементыФормы.Диаграмма.ИсточникДанных = Запрос.Выполнить().Выгрузить();

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

Запустите 1С:Предприятие в режиме отладки и проверьте работу отчета. Обратите внимание на то, что некоторые типы диаграмм выглядят "некрасиво" (график) или не отображаются совсем (изометрическая непрерывная, изометрическая лента). Это связано с тем, что при использовании источника данных нет возможности использовать свойство диаграммы "АвтоТранспонирование" и нужно [206] обрабатывать транспонирование диаграммы "вручную" (используя свойство СерииВСтроках).

Таким образом, на примере этого отчета мы продемонстрировали как создавать запросы, используя конструктор запросов, и как использовать диаграмму для визуализации результата запроса. [207]


Создание периодического регистра сведений Цены


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

Откроем конфигуратор и создадим новый объект конфигурации Регистр сведений. Назовем его "Цены". Установим периодичность этого регистра в пределах секунды.

Перейдем на закладку "Данные" и создадим измерение регистра "Номенклатура" с типом СправочникСсылка.Номенклатура. Укажем, что это измерение будет ведущим.

Создадим измерение "Номенклатура" и укажем, что оно будет ведущим...

Свойство "Ведущее" имеет смысл использовать лишь тогда, когда измерение имеет тип ссылки на объект базы данных. Установка свойства "Ведущее" будет говорить о том, что запись регистра сведений представляет интерес, только пока существует этот объект. При удалении объекта, все записи регистра сведений по этому объекту тоже будут автоматически удалены. Кроме того, в форме списка [122] справочника появляется кнопка командной панели "Перейти", по которой возможен переход к записям регистра, отобранным по значению выбранного элемента справочника.

После этого создадим новый ресурс "Цена", тип Число, длина 15, точность 2, неотрицательное.

Теперь запустим 1С:Предприятие в режиме отладки и посмотрим, как работает наш периодический регистр сведений Цены.

Зададим стоимость услуг нашего ООО "На все руки мастер" следующим образом:

После этого зададим розничные цены на материалы:

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

Теперь посмотрим, как можно использовать заданные нами цены в Документе "ОказаниеУслуги". [123]



Создание плана обмена Филиалы


Теперь займемся созданием "центра" любого алгоритма обмена данными, вокруг которого группируются прочие механизмы – плана обмена. Откроем конфигуратор и создадим новый объект конфигурации ПланОбмена с именем "Филиалы". На закладке "Данные" создадим реквизит плана обмена "Главный", имеющий тип Булево.

Этот реквизит понадобится нам для того, чтобы разрешать коллизии при обмене данными. Под коллизией понимается ситуация, когда один и тот же объект обмена данными был изменен одновременно в двух узлах. В этом случае мы будем анализировать значение реквизита "Главный" и принимать изменения только в том случае, если они сделаны в главном узле. В случае коллизии, изменения, произведенные не в главном узле, мы будем отвергать.

Теперь перейдем на закладку "Прочее" и определим состав объектов обмена данными (кнопка "Состав").

Установим отбор по подсистеме "УчетУслугИМатериалов" и включим в обмен все объекты, относящиеся к этой подсистеме. Проверьте, что константа "ПрефиксНумерации" не участвует в обмене, поскольку ее значение должно быть уникальным для каждой базы, участвующей в обмене.[403]

Состав данных обмена должен выглядеть следующим образом:

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

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

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

Перем РегистрацияВНовыйУзел;

Затем создадим обработчик события формы "ПередЗаписью":




Процедура ПередЗаписью(Отказ)

   РегистрацияВНовыйУзел = ЭтоНовый();

КонецПроцедуры [404]

Этот обработчик и будет устанавливать значение нашей служебной переменной в Истина, в случае записи нового узла плана обмена. После этого создадим обработчик события формы "ПриЗаписи":

Процедура ПриЗаписи(Отказ)

   Если РегистрацияВНовыйУзел Тогда

       //Регистрация изменений всех данных для узла

       ПланыОбмена.ЗарегистрироватьИзменения(Ссылка);

   КонецЕсли;

КонецПроцедуры

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

В заключение, создадим обработчик события формы "ПередОткрытием" для того, чтобы запретить установку реквизита "Главный" для предопределенного узла, соответствующего данной информационной базе:

Процедура ПередОткрытием(Отказ, СтандартнаяОбработка)

   Если Ссылка = ПланыОбмена.Филиалы.ЭтотУзел() Тогда

       ЭлементыФормы.Главный.Доступность = Ложь;

   КонецЕсли;

КонецПроцедуры

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

На этом создание плана обмена завершено, и мы можем перейти непосредственно к созданию процедур обмена данными. [405]


Создание плана видов расчета ОсновныеНачисления


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

Откроем конфигуратор и создадим новый объект конфигурации План видов расчета. Зададим его имя – "ОсновныеНачисления".

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

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

Определим использование периода действия, зависимость от базы и базовые планы видов расчета...

Перейдем на закладку "Прочее" и зададим предопределенные виды расчета. Как и в случае с бухгалтерией, расчеты в нашем OOO "На все [325] руки мастер" будут "скромные", поэтому мы создадим всего три элемента:

·Невыход – с именем и наименованием "Невыход" и кодом "Невых",
·Оклад – с именем, кодом и наименованием "Оклад" и вытесняющим его видом расчета "Невыход",
·Премия – с именем, кодом и наименованием "Премия", с базовым видом расчета "Оклад" и ведущими видами расчета "Невыход" и "Оклад".

Теперь мы перейдем к рассмотрению второго объекта, используемого при реализации механизмов сложных периодических расчетов – регистра расчета. [326]



Создание подсистем


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

Поэтому мы создадим в нашей конфигурации три новых объекта конфигурации Подсистема, которые будут иметь имена: "Бухгалтерия", "РасчетЗарплаты" и "УчетУслугИМатериалов".

Теперь все объекты нашей конфигурации следует отнести к той или иной подсистеме. В следующей таблице представлено соответствие объектов конфигурации и созданных нами подсистем.

Объект конфигурации

Подсистема

Бухгал-

терия

Расчет зарплаты

Учет материалов и услуг

Общие модули

РаботаСДокументами

РаботаСоСправочниками

ПроведениеРасчетов

Справочники

ВариантыНоменклатуры

ВидыГрафиковРаботы

ДополнительныеСвойстваНоменклатуры

Клиенты

Номенклатура

Склады

Сотрудники

Субконто

Документы

ПриходнаяНакладная

ОказаниеУслуги

НачисленияСотрудникам

Перечисления

ВидыНоменклатуры

Отчеты

ВыручкаМастеров [374]

ВыручкаМастеров2

ДиаграммаНачислений

Материалы

НачисленияСотрудникам

ОборотноСальдоваяВедомость

ОстаткиМатериаловПоСвойствам

Перерасчет

ПереченьУслуг

РеестрДокументовОказаниеУслуги

РейтингКлиентов

РейтингУслуг

Универсальный

Универсальный2

УниверсальныйЗ

Планы видов характеристик

ВидыСубконто

СвойстваНоменклатуры

Планы счетов

Основной

Планы видов расчета




ОсновныеНачисления



Регистры сведений

ГрафикиРаботы



ЗначенияСвойствНоменклатуры



Цены



Регистры накопления

ОстаткиМатериалов



СтоимостьМатериалов



Продажи



Регистры бухгалтерии

Управленческий



Регистры расчета

Начисления



Будет удобно задать эти соответствия, воспользовавшись следующей возможностью конфигуратора: установите курсор в корень дерева конфигурации ("Конфигурация") и из контекстного меню правой кнопки мыши выберите пункт "Дополнительно". В появившемся окне перейдите на закладку "Подсистемы".[375]

Теперь, при перемещении по дереву конфигурации, в окне будет отображаться состав подсистем, в которые входит выделенный объект конфигурации:

Зададим принадлежность объектов конфигурации к подсистемам



[376]


Создание процедур обмена данными


Для инициализации обмена данными мы используем объект конфигурации Обработка. Этот объект раньше не встречался нам, поэтому следует сказать о нем несколько слов. С точки зрения структуры и организации работы обработка ничем не отличается от отчета. Разница состоит лишь в том, что обработка обычно используется для того, чтобы выполнить какие-либо действия над информационной базой, а отчет – чтобы получить некоторое визуальное представление данных.



Создание процедуры чтения данных


Порядок создания процедуры чтения данных обмена будет таким же, как и ранее: сначала мы сформируем имя файла, содержащего данные обмена:[410]

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Сначала мы формируем имя файла, которое надеемся найти в этом каталоге, а затем, создав новый объект Файл с таким именем, проверяем, существует ли он. Если такого файла нет, мы завершаем работу процедуры. Если же такой файл найден, нужно будет удить его после того, как все данные, содержащиеся в нем, будут обработаны.

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

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);




   Исключение

       Сообщить("Невозможно открыть файл обмена данными."); [411]

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла);

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Именно в этот момент мы обращаемся к механизмам записи/чтения документов XML, которые работают с ними на "базовом" уровне.

Для этого мы создаем новый объект ЧтениеXML с помощью которого открываем найденный файл для чтения. В случае успеха мы выводим сообщение о начале загрузки данных из файла. В конце процедуры мы также прекращаем чтение XML-данных из файла методом Закрыть().

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

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");



       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла); [412]

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Здесь мы обращаемся к механизмам инфраструктуры сообщений планов обмена и создаем объект ЧтениеСообщенияОбмена. Используя метод этого объекта НачатьЧтение() мы считываем заголовок XML-сообщения, в котором содержится, в том числе, информация об отправителе сообщения. После того, как все сообщение будет нами обработано, мы заканчиваем чтение.

Теперь, когда мы представили данные обмена в виде сообщения и получили его заголовок, можно произвести одну проверку перед тем, как начать собственно обрабатывать данные:

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;



   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------"); [413]

   Сообщить(" - Считывается файл " + ИмяФайла);

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Мы проверяем, является ли отправитель сообщения тем узлом, для которого мы в данном вызове этой процедуры производим обмен данными.

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

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +



       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл [414]

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла);

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   // Удаляем регистрацию изменений

   // для узла отправителя сообщения

   //*** служба регистрации изменений

   ПланыОбмена.УдалитьРегистрациюИзменений(

       ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Обратите внимание, что здесь мы обращаемся к службе регистрации изменений и используем метод



УдалитьРегистрациюИзменений() для выполнения описанных действий.

Теперь, наконец, мы можем приступить к чтению непосредственно самих данных, содержащихся в сообщении:

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" + [415]

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла);

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   //читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   // Удаляем регистрацию изменений

   // для узла отправителя сообщения

   //*** служба регистрации изменений



   ПланыОбмена.УдалитьРегистрациюИзменений(

       ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);

   // Читаем данные из сообщения

   //*** XML-сериалшация

   Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл

   КонецЦикла;

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры [416]

Чтение данных выполняется в цикле, причем мы снова обращаемся к механизмам XML-сериализации и методом глобального контекста (ВозможностьЧтенияXML()) получаем очередной тип данных XML из объекта ЧтениеXML и определяем, имеется ли соответствующий тип 1С:Предприятия. В случае успеха выполнение цикла продолжается.

И первое, что нам нужно сделать – представить данные XML в виде некоторого значения, имеющего тип 1С:Предприятия. Для этого мы используем метод глобального контекста ПрочитатьXML():

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");



       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла);

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   // Удаляем регистрацию изменений [417]

   // для узла отправителя сообщения

   //*** служба регистрации изменений

   ПланыОбмена.УдалитьРегистрациюИзменений(

       ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);

   // Читаем данные из сообщения

   //*** XML-сериалшация

   Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл

       // Читаем очередное значение

       Данные = ПрочитатьXML(ЧтениеXML);

   КонецЦикла;

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

В результате выполнения этого метода переменная "Данные" будет содержать объект 1С:Предприятия, соответствующий данным XML.

Теперь, после того, как объект 1С:Предприятия получен, следует разрешить возможную коллизию:

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла



   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда

       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла); [418]

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   // Удаляем регистрацию изменений

   // для узла отправителя сообщения

   //*** служба регистрации изменений

   ПланыОбмена.УдалитьРегистрациюИзменений(

       ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);

   // Читаем данные из сообщения

   //*** XML-сериалшация

   Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл



       // Читаем очередное значение

       Данные = ПрочитатьXML(ЧтениеXML);

       // Не переносим изменение полученное в главный из неглавного

       // если есть регистрация изменения

       Если Не ЧтениеСообщения.Отправитель.Главный И

           ПланыОбмена.ИзменениеЗарегистрировано(ЧтениеСообщения.Отправитель, Данные) Тогда

           Сообщить("- Изменения отклонены");

           Продолжить;

       КонецЕсли;

   КонецЦикла;

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

Возможная коллизия разрешается следующим образом: мы проверяем, является ли узел-отправитель главным узлом и есть ли записи об изменении этого объекта для этого узла в нашей базе [419] данных. В случае если объект изменялся в нашей базе и отправитель не является главным узлом, мы отклоняем запись полученного объекта. Во всех остальных случаях мы принимаем изменения полученного объекта.

Теперь единственное, что нам осталось сделать – записать полученные данные:

Процедура ПрочитатьСообщениеСИзменениями() Экспорт

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя файла

   ИмяФайла = Каталог + ?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(Ссылка.Код) + "_" + СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + ".xml";

   Файл = Новый Файл(ИмяФайла);

   Если Не Файл.Существует() Тогда



       Возврат;

   КонецЕсли;

   //*** Чтение документов XML

   // Попытаться открыть файл

   ЧтениеXML = Новый ЧтениеXML;

   Попытка

       ЧтениеXML.ОткрытьФайл(ИмяФайла);

   Исключение

       Сообщить("Невозможно открыть файл обмена данными.");

       Возврат;

   КонецПопытки;

   Сообщить("------- Загрузка из " + Строка(ЭтотОбъект) + "-------");

   Сообщить(" - Считывается файл " + ИмяФайла);

   // Загрузить из найденного файла

   //*** Инфраструктура сообщений

   ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();

   // читать заголовок сообщения обмена данными - файла XML

   ЧтениеСообщения.НачатьЧтение(ЧтениеXML);

   // Сообщение предназначено не для этого узла

   Если ЧтениеСообщения.Отправитель <> Ссылка Тогда

       ВызватьИсключение "Неверный узел";

   КонецЕсли;

   // Удаляем регистрацию изменений

   // для узла отправителя сообщения

   //*** служба регистрации изменений [420]

   ПланыОбмена.УдалитьРегистрациюИзменений(

       ЧтениеСообщения.Отправитель, ЧтениеСообщения.НомерПринятого);

   // Читаем данные из сообщения

   //*** XML-сериалшация

   Пока ВозможностьЧтенияXML(ЧтениеXML) Цикл

       // Читаем очередное значение

       Данные = ПрочитатьXML(ЧтениеXML);

       // Не переносим изменение полученное в главный из неглавного

       // если есть регистрация изменения

       Если Не ЧтениеСообщения.Отправитель.Главный И



           ПланыОбмена.ИзменениеЗарегистрировано(ЧтениеСообщения.Отправитель, Данные) Тогда

           Сообщить("- Изменения отклонены");

           Продолжить;

       КонецЕсли;

       // Записать полученные данные

       Данные.ОбменДанными.Отправитель = ЧтениеСообщения.Отправитель;

       Данные.ОбменДанными.Загрузка = Истина;

       Данные.Записать();

   КонецЦикла;

   ЧтениеСообщения.ЗакончитьЧтение();

   ЧтениеXML.Закрыть();

   УдалитьФайлы(ИмяФайла);

   Сообщить("------------ Конец загрузки ------------");

КонецПроцедуры

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

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

На этом создание процедуры получения и обработки данных обмена закончено. [421]


Создание процедуры расчета записей регистра Начисления


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

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

Откроем в конфигураторе текст обработчика проведения документа "НачислениеСотрудникам" и добавим в него вызов процедуры из общего модуля:

Процедура ОбработкаПроведения(Отказ, Режим)

//{{__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

   // Данный фрагмент построен конструктором.

   // При повторном использовании конструктора, внесенные вручную изменения будут утеряны!!!

   Для Каждого ТекСтрокаНачисления Из Начисления Цикл

       // регистр Начисления

       Движение = Движения.Начисления.Добавить();

       Движение.Сторно = Ложь;

       Движение.ВидРасчета = ТекСтрокаНачисления.ВидРасчета;

       Движение.ПериодДействияНачало = ТекСтрокаНачисления.ДатаНачала;

       Движение.ПериодДействияКонец = КонецДня(ТекСтрокаНачисления.ДатаОкончания);

       Движение.ПериодРегистрации = ТекСтрокаНачисления.ДатаНачала;

       Движение.БазовыйПериодНачало = ТекСтрокаНачисления.ДатаНачала;

       Движение.БазовыйПериодКонец = КонецДня(ТекСтрокаНачисления.ДатаОкончания);

       Движение.Сотрудник = ТекСтрокаНачисления.Сотрудник;

       Движение.ГрафикРаботы = ТекСтрокаНачисления.ГрафикРаботы;

       Движение.ИсходныеДанные = ТекСтрокаНачисления.Результат;




   КонецЦикла;

   // записываем движения регистров

   Движения.Начисления.Записать();

   // получим список всех сотрудников, содержащихся в документе

   Запрос = Новый Запрос(

      "ВЫБРАТЬ РАЗЛИЧНЫЕ

      |    НачисленияСотрудникамНачисления.Сотрудник [349]

      |ИЗ

      |    Документ.НачисленияСотрудникам.Начисления КАК НачисленияСотрудникамНачисления

      |ГДЕ

      |    НачисленияСотрудникамНачисления.Ссылка = &ТекущийДокумент");

   Запрос.УстановитьПараметр("ТекущийДокумент", Ссылка);

   //сформируем список сотрудников

   ТаблЗнач = Запрос.Выполнить().Выгрузить();

   МассивСотрудников = ТаблЗнач.ВыгрузитьКолонку("Сотрудник");

   СписокСотрудников = Новый СписокЗначений;

   СписокСотрудников.ЗагрузитьЗначения(МассивСотрудников);

   РасчитатьНачисления(Движения.Начисления, ПланыВидовРасчета.ОсновныеНачисления.Оклад, СписокСотрудников);

   Движения.Начисления.Записать(,Истина);

   РасчитатьНачисления(Движения.Начисления, ПланыВидовРасчета.ОсновныеНачисления.Премия, СписокСотрудников);

   Движения.Начисления.Записать(,Истина);

   //}}__КОНСТРУКТОР_ДВИЖЕНИЙ_РЕГИСТРОВ

КонецПроцедуры

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



Перед вызовом процедуры из общего модуля мы формируем список сотрудников, содержащихся в документе, чтобы передать его в вызываемую процедуру. [350]

Теперь создадим в ветке "Общие" новый общий модуль "ПроведениеРасчетов". Добавим в него заготовку процедуры "РасчитатьНачисления":

Процедура РасчитатьНачисления(НаборЗаписейРегистра, ТребуемыйВидРасчета, СписокСотрудников) Экспорт

   //Рассчитать первичные записи

   Если ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

   //Рассчитать вторичные записи

   ИначеЕсли ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

   КонецЕсли;

КонецПроцедуры

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

При расчете первичных записей нам понадобятся данные графика из регистра расчета, поэтому добавим в первую ветку условия запрос по виртуальной таблице регистра расчета "РегистрРасчета.Начисления.ДанныеГрафика":

Процедура РасчитатьНачисления(НаборЗаписейРегистра, ТребуемыйВидРасчета, СписокСотрудников) Экспорт

   //Рассчитать первичные записи

   Если ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

       Запрос = Новый Запрос;

       Запрос.Текст =

       "ВЫБРАТЬ

       |    НачисленияДанныеГрафика.ЗначениеПериодДействия КАК Норма,

       |    НачисленияДанныеГрафика.ЗначениеФактическийПериодДействия КАК Факт,

       |    НачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки

       |ИЗ

       |    РегистрРасчета.Начисления.ДанныеГрафика(



       |        Регистратор = &Регистратор

       |            И ВидРасчета = &ВидРасчета

       |            И Сотрудник В (&СписокСотрудников)) КАК НачисленияДанныеГрафика";

       Запрос.УстановитьПараметр("Регистратор", НаборЗаписейРегистра.Отбор.Регистратор.Значение);[351]

       Запрос.УстановитьПараметр("ВидРасчета", ТребуемыйВидРасчета);

       Запрос.УстановитьПараметр("СписокСотрудников",СписокСотрудников);

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

   //Рассчитать вторичные записи

   ИначеЕсли ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

   КонецЕсли;

КонецПроцедуры

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

Теперь добавим обход переданного в процедуру набора записей и расчет записей, для которых получены значения графика:

Процедура РасчитатьНачисления(НаборЗаписейРегистра, ТребуемыйВидРасчета, СписокСотрудников) Экспорт

   //Рассчитать первичные записи

   Если ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

      Запрос = Новый Запрос;

      Запрос.Текст =

      "ВЫБРАТЬ

      |    НачисленияДанныеГрафика.ЗначениеПериодДействия КАК Норма,



      |    НачисленияДанныеГрафика.ЗначениеФактическийПериодДействия КАК Факт,

      |    НачисленияДанныеГрафика.НомерСтроки КАК НомерСтроки

      |ИЗ

      |    РегистрРасчета.Начисления.ДанныеГрафика(

      |        Регистратор = &Регистратор

      |            И ВидРасчета = &ВидРасчета

      |            И Сотрудник В (&СписокСотрудников)) КАК НачисленияДанныеГрафика";

      Запрос.УстановитьПараметр("Регистратор", НаборЗаписейРегистра.Отбор.Регистратор.Значение);

      Запрос.УстановитьПараметр("ВидРасчета", ТребуемыйВидРасчета);

      Запрос.УстановитьПараметр("СписокСотрудников",СписокСотрудников);[352]

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

      Для Каждого ЗаписьРегистра Из НаборЗаписейРегистра Цикл

          СтруктураНомер = Новый Структура("НомерСтроки");

          СтруктураНомер.НомерСтроки = ЗаписьРегистра.НомерСтроки;

          ВыборкаРезультата.Сбросить();

          Если ВыборкаРезультата.НайтиСледующий(СтруктураНомер) Тогда

              Если ВыборкаРезультата.Норма = 0 Тогда

                  Сообщить("Вид расчета: Оклад - Нет рабочих дней в заданном периоде",);



                  ЗаписьРегистра.Результат = 0;

              Иначе

                  // рассчитать оклад по фактическому периоду и исходным данным

                  ЗаписьРегистра.Результат = (ЗаписьРегистра.ИсходныеДанные / ВыборкаРезультата.Норма) * ВыборкаРезультата.Факт;

                  Сообщить("Выполнен расчет "+ЗаписьРегистра.Регистратор+" - "+ЗаписьРегистра.ВидРасчета+" - "+ЗаписьРегистра.Сотрудник,);

              КонецЕсли;

          КонецЕсли;

      КонецЦикла;

   //Рассчитать вторичные записи

   ИначеЕсли ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

   КонецЕсли;

КонецПроцедуры

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

Теперь добавим текст запроса во вторую ветку условия Если... с той лишь разницей, что теперь мы будем получать значения базы, используя виртуальную таблицу регистра расчета "РегистрРасчета.Начисления.БазаНачисления":

Процедура РасчитатьНачисления(НаборЗаписейРегистра, ТребуемыйВидРасчета, СписокСотрудников) Экспорт

   //Рассчитать первичные записи

   Если ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

...



   // Рассчитать вторичные записи [353]

   ИначеЕсли ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

       Запрос = Новый Запрос;

       Запрос.Текст =

           "ВЫБРАТЬ

           |    НачисленияБазаНачисления.РезультатБаза КАК База,

           |    НачисленияБазаНачисления.НомерСтроки КАК НомерСтроки

           |ИЗ

           |    РегистрРасчета.Начисления.БазаНачисления(

           |        &ИзмеренияОсновного,

           |        &ИзмеренияБазового,

           |        ,

           |        Регистратор = &Регистратор

           |           И ВидРасчета = &ВидРасчета

           |           И Сотрудник В (&СписокСотрудников)) КАК НачисленияБазаНачисления";

           Измер = Новый Массив(1);

           Измер[0] = "Сотрудник";

           Запрос.УстановитьПараметр("ИзмеренияОсновного", Измер);

           Запрос.УстановитьПараметр("ИзмеренияБазового", Измер);



           Запрос.УстановитьПараметр("Регистратор", НаборЗаписейРегистра.Отбор.Регистратор.Значение);

           Запрос.УстановитьПараметр("ВидРасчета",ТребуемыйВидРасчета);

           Запрос.УстановитьПараметр("СписокСотрудников", СписокСотрудников);

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

   КонецЕсли;

КонецПроцедуры

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

В заключение осталось добавить во второе условие Если... обход набора записей регистра расчета и вычисление результата вторичных записей:

Процедура РасчитатьНачисления(НаборЗаписейРегистра, ТребуемыйВидРасчета, СписокСотрудников) Экспорт

   //Рассчитать первичные записи

   Если ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Оклад Тогда

... [354]

   //Рассчитать вторичные записи

   ИначеЕсли ТребуемыйВидРасчета = ПланыВидовРасчета.ОсновныеНачисления.Премия Тогда

       Запрос = Новый Запрос;

       Запрос.Текст =

           "ВЫБРАТЬ

           |    НачисленияБазаНачисления.РезультатБаза КАК База,

           |    НачисленияБазаНачисления.НомерСтроки КАК НомерСтроки

           |ИЗ

           |    РегистрРасчета.Начисления.БазаНачисления(



           |        &ИзмеренияОсновного,

           |        &ИзмеренияБазового,

           |        ,

           |        Регистратор = &Регистратор

           |           И ВидРасчета = &ВидРасчета

           |           И Сотрудник В (&СписокСотрудников)) КАК НачисленияБазаНачисления";

           Измер = Новый Массив(1);

           Измер[0] = "Сотрудник";

           Запрос.УстановитьПараметр("ИзмеренияОсновного", Измер);

           Запрос.УстановитьПараметр("ИзмеренияБазового", Измер);

           Запрос.УстановитьПараметр("Регистратор", НаборЗаписейРегистра.Отбор.Регистратор.Значение);

           Запрос.УстановитьПараметр("ВидРасчета",ТребуемыйВидРасчета);

           Запрос.УстановитьПараметр("СписокСотрудников", СписокСотрудников);

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

           Для Каждого ЗаписьРегистра Из НаборЗаписейРегистра Цикл

               СтруктураНомер = Новый Структура("НомерСтроки");



               СтруктураНомер.НомерСтроки = ЗаписьРегистра.НомерСтроки;

               ВыборкаРезультата.Сбросить();

               Если ВыборкаРезультата.НайтиСледующий(СтруктураНомер) Тогда

                   ЗаписьРегистра.Результат = ВыборкаРезультата.База * (10/100);

                   Сообщить("Выполнен расчет "+ЗаписьРегистра.Регистратор+" - "+ЗаписьРегистра.ВидРасчета+" - "+ЗаписьРегистра.Сотрудник,);

               КонецЕсли;

           КонецЦикла;

   КонецЕсли;

КонецПроцедуры

Сумму начисленной премии мы рассчитываем как 10% от рассчитанной оплаты по окладу.[355]

Запустим 1С:Предприятие в режиме отладки и проверим правильность работы процедуры расчета.

Отменим проведение документа Начисление сотрудникам №3 и перепроведем документы Начисление сотрудникам №1 и №2. Регистр расчета Начисления должен выглядеть следующим образом:



Гусакову и Деловому начислена премия в размере 10% от суммы начисления по окладу.

Проведем документ Начисление сотрудникам №3, а затем №1 и№2. Состояние регистра изменится следующим образом:



В результате невыхода Гусакова на работу, сумма его оплаты по окладу будет уменьшена и соответствующим образом уменьшится начисленная ему премия. [356]


Создание процедуры записи данных


Сами процедуры записи и чтения данных обмена мы разместим в модуле объекта План обмена "Филиалы". Сначала создадим процедуру, которая используется нами при обмене данными – "ЗаписатьСообщениеСИзменениями". Порядок создания этой процедуры будет следующим: сначала мы сформируем имя файла, который будет содержать данные для обмена:

Процедура ЗаписатьСообщениеСИзменениями() Экспорт

   Сообщить("------------ Выгрузка в узел" + Строка(ЭтотОбъект) + "------------");

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя временного файла

   ИмяФайла = Каталог +?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "_" + СокрЛП(Ссылка.Код) + ".xml";

   Сообщить("------------ Конец выгрузки ------------");

КонецПроцедуры

Для упрощения примера, мы будем обмениваться сообщениями через каталог временных файлов. Имена сообщений стандартизованы и имеют вид "МеssаgеКодУзлаОтправителя_КодУзлаПолучателя.xml".

После этого обратимся к механизмам записи/чтения XML документов и создадим новый объект ЗаписьXML с помощью которого откроем новый XML файл для записи, запишем в него [407] объявление XML, и в конце процедуры завершим запись закроем файл:

Процедура ЗаписатьСообщениеСИзменениями() Экспорт

   Сообщить("------------ Выгрузка в узел" + Строка(ЭтотОбъект) + "------------");

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя временного файла

   ИмяФайла = Каталог +?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "_" + СокрЛП(Ссылка.Код) + ".xml";

   // Создать объект записи XML




   //*** запись XML документов

   ЗаписьXML = Новый ЗаписьXML;

   ЗаписьXML.ОткрытьФайл(ИмяФайла);

   ЗаписьXML.ЗаписатьОбъявлениеXML();

   ЗаписьXML.Закрыть();

   Сообщить("------------ Конец выгрузки ------------");

КонецПроцедуры

Теперь мы обратимся к механизмам инфраструктуры сообщений и создадим новый объект ЗаписьСообщенияОбмена, метод которого НачатьЗапись() позволяет, кроме всего прочего, создать очередной номер сообщения и записать заголовок сообщения в XML. В конце процедуры мы опять же закончим запись сообщения:

Процедура ЗаписатьСообщениеСИзменениями() Экспорт

   Сообщить("------------ Выгрузка в узел" + Строка(ЭтотОбъект) + "------------");

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя временного файла

   ИмяФайла = Каталог +?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "_" + СокрЛП(Ссылка.Код) + ".xml";

   // Создать объект записи XML

   //*** запись XML документов

   ЗаписьXML = Новый ЗаписьXML;

   ЗаписьXML.ОткрытьФайл(ИмяФайла);

   ЗаписьXML.ЗаписатьОбъявлениеXML();

   //*** инфраструктура сообщений

   ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();

   ЗаписьСообщения.НачатьЗапись(ЗаписьХМL, Ссылка);

   Сообщить(" Номер сообщения; " + ЗаписьСообщения.НомерСообщения); [408]

   ЗаписьСообщения.ЗакончитьЗапись();

   ЗаписьXML.Закрыть();

   Сообщить("------------ Конец выгрузки ------------");

КонецПроцедуры

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



При формировании выборки мы передаем вторым параметром номер сообщения, которым эти данные будут переданы:

Процедура ЗаписатьСообщениеСИзменениями() Экспорт

   Сообщить("------------ Выгрузка в узел" + Строка(ЭтотОбъект) + "------------");

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя временного файла

   ИмяФайла = Каталог +?(Прав(Каталог, 1) = "\","", "\") + "Message" +

       СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "_" + СокрЛП(Ссылка.Код) + ".xml";

   // Создать объект записи XML

   //*** запись XML документов

   ЗаписьXML = Новый ЗаписьXML;

   ЗаписьXML.ОткрытьФайл(ИмяФайла);

   ЗаписьXML.ЗаписатьОбъявлениеXML();

   //*** инфраструктура сообщений

   ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();

   ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Ссылка);

   Сообщить(" Номер сообщения; " + ЗаписьСообщения.НомерСообщения);

   // Получить выборку измененных данных

   //*** механизм регистрации изменений

   ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(

       ЗаписьСообщения.Получатель, ЗаписьСообщения.НомерСообщения);

   ЗаписьСообщения.ЗакончитьЗапись();

   ЗаписьXML.Закрыть();

   Сообщить("------------ Конец выгрузки ------------");

КонецПроцедуры [409]

Теперь осталось только перебрать выборку записей в цикле сериализовать их в открытый XML файл:

Процедура ЗаписатьСообщениеСИзменениями() Экспорт

   Сообщить("------------ Выгрузка в узел" + Строка(ЭтотОбъект) + "------------");

   Каталог = КаталогВременныхФайлов();

   // Сформировать имя временного файла

   ИмяФайла = Каталог +?(Прав(Каталог, 1) = "\","", "\") + "Message" +



       СокрЛП(ПланыОбмена.Филиалы.ЭтотУзел().Код) + "_" + СокрЛП(Ссылка.Код) + ".xml";

   // Создать объект записи XML

   //*** запись XML документов

   ЗаписьXML = Новый ЗаписьXML;

   ЗаписьXML.ОткрытьФайл(ИмяФайла);

   ЗаписьXML.ЗаписатьОбъявлениеXML();

   //*** инфраструктура сообщений

   ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();

   ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Ссылка);

   Сообщить(" Номер сообщения; " + ЗаписьСообщения.НомерСообщения);

   // Получить выборку измененных данных

   //*** механизм регистрации изменений

   ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(

       ЗаписьСообщения.Получатель, ЗаписьСообщения.НомерСообщения);

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

       // Записать данные в сообщение

       //***XML-сериализация

       ЗаписатьXML(ЗаписьXML, ВыборкаИзменений.Получить());

   КонецЦикла;

   ЗаписьСообщения.ЗакончитьЗапись();

   ЗаписьXML.Закрыть();

   Сообщить("------------ Конец выгрузки ------------");

КонецПроцедуры

На этом создание процедуры записи данных обмена закончено.


Создание регистра бухгалтерии Управленческий


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

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

Теперь перейдем на закладку "Данные" и создадим два ресурса:

·"Сумма", длина 15, точность 2, балансовый,
·"Количество", длина 15, точность 3, небалансовый, признак учета – количественный, признак учета субконто – количественный.

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

Запустим 1С:Предприятие в режиме отладки и откроем регистр бухгалтерии "Управленческий". Как видите, платформа (при создании структуры хранения данных) добавила к созданным нами реквизитам регистра еще ряд полей, которые явились следствием использования плана счетов "Основной". Прежде всего, это поля "СчетДт", "СубконтоДт1", "СчетКт" и "СубконтоКт1". Кроме этого, если прокрутить окно вправо до конца, то вы обнаружите две колонки "Количество". Это "КоличествоДт" и "КоличествоКт". Для измерений и ресурсов регистра, связанных с признаками учета, платформа создает пару полей для хранения значения ресурса отдельно по дебету и отдельно по кредиту проводки.[302]



Создание регистра расчета Начисления


Прежде, чем мы начнем создавать объект конфигурации Регистр расчета "Начисления", нам потребуется создать два дополнительных объекта конфигурации – регистр сведений "ГрафикиРаботы" и справочник "ВидыГрафиковРаботы". Справочник понадобится нам для того, чтобы хранить информацию о том, какие графики работы существуют в OOO "На все руки мастер", а регистр сведений – для указания того, какие дни в месяце являются рабочими, поскольку сумма оплаты по окладу будет рассчитываться исходя из того, сколько дней отработал сотрудник в расчетном месяце. Откроем конфигуратор и создадим новый объект конфигурации Справочник с именем "ВидыГрафиковРаботы".

В этом справочнике у нас будет два предопределенных графика работы – "ГрафикАдминистрации" и "ГрафикМастеров".

После этого создадим объект конфигурации Регистр сведений с именем "ГрафикиРаботы". Этот регистр будет иметь два измерения:

·"ГрафикРаботы", тип СправочникСсылка.ВидыГрафиковРаботы,
·"Дата", тип Дата.

Затем создадим единственный ресурс регистра – "Значение", с типом Число, длиной 1.

Запустим 1С:Предприятие в режиме отладки и заполним регистр данными о рабочих днях марта графика мастеров. Чтобы проще выполнить эту довольно однообразную работу, можете воспользоваться возможностью добавления элементов в справочник [335] копированием (Действия

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

Теперь все готово для создания регистра расчета.

Создадим новый объект конфигурации Регистр расчета с именем "Начисления". В качестве плана видов расчета, используемого регистром, выберем план видов расчетов "ОсновныеНачисления". Установим, что регистр будет использовать период действия, график будет задаваться в регистре сведений "ГрафикиРаботы", значение графика будет находиться в ресурсе "Значение", а дата графика – в измерении "Дата".[336]




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



Затем перейдем на закладку "Данные" и создадим:

·измерение "Сотрудник", тип СправочникСсылка.Сотрудники, базовое,
·ресурс "Результат", тип Число, длина 15, точность 2,
·реквизит "ГрафикРаботы", тип СправочникСсылка.ВидыГрафиковРаботы, связь с графиком по измерению "ГрафикРаботы",
·реквизит "ИсходныеДанные", тип Число, длина 15, точность 2.
Реквизит "ГрафикРаботы" мы будем использовать для того, чтобы связать запись регистра с используемым графиком работы, а реквизит "ИсходныеДанные" – чтобы хранить в нем данные, которые могут понадобиться при расчете или перерасчете (в нашем примере это будет расчет оклада).[337]

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

На этом создание объекта конфигурации Регистр расчета "Начисления" завершено.[338]


Создание регистра СтоимостьМатериалов


Регистр "СтоимостьМатериалов" совсем не сложен, поэтому мы не будем подробно останавливаться на его создании. Этот регистр будет иметь всего одно измерение – "Материал" с типом СправочникСсылка.Номенклатура и один ресурс – "Стоимость" с длиной 15 и точностью 2.

После создания, регистр "СтоимостьМатериалов" должен выглядеть в дереве конфигурации следующим образом:

Теперь мы можем приступить к внесению изменений в процедуры проведения документов.

Начнем с самого простого – документа "ПриходнаяНакладная". [140]



Создание ролей


При создании ролей исходят, как правило, из того, какие полномочия требуются различным группам пользователей на доступ к информации. Для этого ролей мы воспользуемся подсистемами, которые значительно облегчат нашу задачу. Первая роль, которую мы создадим, будет роль "Администратор". Она должна включать в себя полные права на работу с данными информационной базы. [379]

Создадим новый объект конфигурации Роль с именем "Администратор". Откроется окно редактирования прав:

Слева, в списке объектов, перечислены все объекты и виды объектов конфигурации, а справа, в окне прав, перечислены доступные права для выбранного объекта или видов объектов конфигурации. Пробежав по списку объектов, вы обнаружите, что по умолчанию новая роль имеет полные права на все объекты и виды объектов. В данном случае нас это устраивает, поскольку администратор и должен иметь такие права. Единственное, что следует сделать – снять разрешение на интерактивное удаления для всех объектов. Это необходимо для того, чтобы администратор случайно не мог удалить какой-либо объект базы данных.

Следующей ролью, которую мы создадим, будет роль "ТолькоПросмотр". Создадим новый объект конфигурации Роль с именем "ТолькоПросмотр" и в открывшемся окне редактирования прав выполним команду Действия

Снять все права. В результате этого, все права на доступ ко всем объектам будут сняты, за исключением тех видов объектов конфигурации, для которых не [380] создано ни одного объекта. Для таких видов объектов конфигурации останутся установлены полные права.

Теперь нам останется лишь пройти по видам объектов конфигурации и установить для них права "Чтение", "Просмотр" и "Использование". Вторая роль нашей конфигурации готова.

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

Установить по подсистемам и выберем подсистему "УчетМатериаловИУслуг". В результате будут установлены все права на объекты конфигурации, относящиеся к данной подсистеме. [381]




Если теперь установить фильтр объектов по подсистем "УчетМатериаловИУслуг", то можно, при необходимости, внести уточнения в установленные права:

Установим фильтр по подсистеме...



В частности, для справочника "Сотрудники" мы запретим добавление, изменение и удаление. Обратите внимание, что при запрете права "Добавление" исчезла отметка и у права "Интерактивное добавление", т.к. оно является "уточнением" права "Добавление". Точно также "уточненные" права запрещаются и при отмене прав на изменение и удаление.

Кроме этого мы снова снимем разрешения на интерактивное удаление для всех объектов базы данных. [382]

В заключение нам с вами осталось создать две роли: "Бухгалтер" и "Расчетчик". Мы разделим права по расчету зарплаты и по ведению бухгалтерского учета. Дело в том, что в OOO "На все руки мастер" есть бухгалтер и помощник бухгалтера. Помощник бухгалтера занят, в основном, расчетом зарплаты, но иногда это делает и главный бухгалтер. Поэтому ему необходимо будет назначить обе роли, в то время как помощнику – только роль "Расчетчик".

Создадим новый объект конфигурации Роль с именем "Расчетчик". В окне редактирования прав снимем все права и затем установим их по подсистеме "РасчетЗарплаты" (и не забудем запретить интерактивное удаление).

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

Список прав для каждой роли можно получить, выполнив в окне редактирования прав команду Действия
Вывести список.

Теперь мы можем перейти к созданию интерфейсов. [383]


Создание универсального отчета


Мы с вами рассмотрим один из вариантов использования построителя отчета, в котором результаты отчета будут выводиться в сводную таблицу.

Создадим в конфигураторе новый объект конфигурации Отчет "Универсальный". Этот отчет будет иметь реквизит "ПостроительОтчета", с типом ПостроительОтчета.

В модуле отчета создадим текст запроса для построителя отчета. Для этого вставим в модуль следующие строки:

ПостроительОтчета.Текст =

;

Установим курсор перед символом точки с запятой и вызовем конструктор запросов (Текст

Конструктор запроса...).

В качестве таблицы выберем виртуальную таблицу регистра накопления "Продажи.Обороты". Из этой таблицы выберем все поля:

Ha закладке "Итоги" укажем получение общих итогов и выберем все ресурсы регистра:


[211]

Нажмем "OK". B модуле отчета появится текст сформированного запроса:

ПостроительОтчета.Текст =

"ВЫБРАТЬ

|    ПродажиОбороты.Номенклатура,

|    ПродажиОбороты.Номенклатура.Представление,

|    ПродажиОбороты.Клиент,

|    ПродажиОбороты.Клиент.Представление,

|    ПродажиОбороты.Мастер,

|    ПродажиОбороты.Мастер.Представление,

|    ПродажиОбороты.КоличествоОборот КАК КоличествоОборот,

|    ПродажиОбороты.ВыручкаОборот КАК ВыручкаОборот,

|    ПродажиОбороты.СтоимостьОборот КАК СтоимостьОборот

|ИЗ

|    РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты

|ИТОГИ

|    СУММА(КоличествоОборот),

|    СУММА(ВыручкаОборот),

|    СУММА(СтоимостьОборот)

|ПО

|    ОБЩИЕ";

Добавим в текст запроса указания для построителя отчетов:

ПостроительОтчета.Текст =

"ВЫБРАТЬ

|    ПродажиОбороты.Номенклатура,

|    ПродажиОбороты.Номенклатура.Представление,

|    ПродажиОбороты.Клиент,




|    ПродажиОбороты.Клиент.Представление,

|    ПродажиОбороты.Мастер,

|    ПродажиОбороты.Мастер.Представление,

|    ПродажиОбороты.КоличествоОборот КАК КоличествоОборот,

|    ПродажиОбороты.ВыручкаОборот КАК ВыручкаОборот,

|    ПродажиОбороты.СтоимостьОборот КАК СтоимостьОборот

|ИЗ

|    РегистрНакопления.Продажи.Обороты КАК ПродажиОбороты

|{ИТОГИ ПО Номенклатура, Клиент, Мастер}

|ИТОГИ

|    СУММА(КоличествоОборот),

|    СУММА(ВыручкаОборот),

|    СУММА(СтоимостьОборот)

|ПО

|    ОБЩИЕ";[212]

Теперь создадим форму отчета и расположим на ней поле табличного документа (Форма
Вставить элемент управления
Поле табличного документа) с именем "ПолеТабличногоДокумента":

Расположим в форме поле табличного документа



Затем установим курсор в верхнюю левую ячейку поля табличного документа и выполним Таблица
Встроенные таблицы
Вставить сводную таблицу.

После этого создадим обработчик события формы отчета "При открытии", и добавим в него следующий текст:

Процедура ПриОткрытии()

   СводнаяТаблица = ЭлементыФормы.ПолеТабличногоДокумента.ВстроенныеТаблицы.СводнаяТаблица1;

   СводнаяТаблица.ИсточникДанных = ПостроительОтчета;

КонецПроцедуры

Этим текстом мы устанавливаем сводной таблице в качестве источника данных построитель отчета.[213]

Запустим 1С:Предприятие в режиме отладки и откроем отчет "Универсальный". На экране появится форма отчета, и окно выбора полей сводной таблицы:



Поместим значение ресурса "ВыручкаОборот" в данные, измерение "Номенклатура" в строки, а измерение "Мастер" в колонки. Отчет примет вид:



[214]

Теперь в окне выбора полей сводной таблицы раскроем группу "Номенклатура" и добавим значение "(Без иерархии)" в строки, а измерение "Клиент" добавим в колонки. Отчет изменит свой вид:



Таким образом, используя построитель отчета, мы предоставили пользователю возможность самостоятельно формировать отчет по регистру "ОказанныеУслуги" в том виде, который ему нужен.[215]


Универсальный отчет


На примере создания универсального отчета мы познакомимся с объектом встроенного языка ПостроительОтчета и узнаем как работать со сводной таблицей.



Выполнение перерасчета записей регистра расчета


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

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

Поскольку единственным способом получения итоговой информации о начислениях сотрудникам в нашей конфигурации является отчет "НачисленияСотрудникам", для вызова этой процедуры мы добавим кнопку "Перерасчитать" в командную панель "ДействияФормы":

В обработчик события нажатия кнопки вставим текст вызова процедуры перерасчета:

Процедура ДействияФормыПерерасчитать(Кнопка)

ПерерасчитатьНачисления(ПланыВидовРасчета.ОсновныеНачисления.Оклад);

   ПерерасчитатьНачисления(ПланыВидовРасчета.ОсновныеНачисления.Премия);

КонецПроцедуры

Саму процедуру перерасчета разместим в общем модуле "ПроведениеРасчетов":

Процедура ПерерасчитатьНачисления(ТребуемыйВидРасчета) Экспорт

   //здесь следует выбрать из набора записей перерасчета, записи

   //в следующей последовательности:

   //записи документа1 для сотрудников из списка, [359]

   //записи документа2 для сотрудников из списка,

   //и т.д.

   Запрос = Новый Запрос(

       "ВЫБРАТЬ

       |    НачисленияПерерасчет.ОбъектПерерасчета КАК ОбъектПерерасчета,

       |    НачисленияПерерасчет.Сотрудник

       |ИЗ

       |    РегистрРасчета.Начисления.Перерасчет КАК НачисленияПерерасчет




       |ГДЕ

       |    НачисленияПерерасчет.ВидРасчета = &ТребуемыйВидРасчета

       |ИТОГИ ПО

       |    ОбъектПерерасчета");

   Запрос.УстановитьПараметр("ТребуемыйВидРасчета", ТребуемыйВидРасчета);

   СписокСотрудников = Новый СписокЗначений;

   //перебрать группировку по регистратору

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

   Пока ВыборкаПоРегистратору.Следующий() Цикл

       Регистратор = ВыборкаПоРегистратору.ОбъектПерерасчета;

       //перебрать группировку по сотрудникам

       //для выбранного регистратора

       //и создать список сотрудников

       ВыборкаПоСотрудникам = ВыборкаПоРегистратору.Выбрать();

       СписокСотрудников.Очистить();

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

           СписокСотрудников.Добавить(ВыборкаПоСотрудникам.Сотрудник);

       КонецЦикла;

       // получить набор записей регистра расчета

       //для выбранного регистратора

       НаборЗаписей = РегистрыРасчета.Начисления.СоздатьНаборЗаписей();

       НаборЗаписей.Отбор.Регистратор.Значение = Регистратор;

       НаборЗаписей.Прочитать();

       РасчитатьНачисления(НаборЗаписей, ТребуемыйВидРасчета, СписокСотрудников);

       НаборЗаписей.Записать(,Истина);

       //очистить перерасчитанные записи в перерасчете



       НаборЗаписейПерерасчета = РегистрыРасчета.Начисления.Перерасчеты.Перерасчет.СоздатьНаборЗаписей();

       НаборЗаписейПерерасчета.Отбор.ОбъектПерерасчета.Значение = Регистратор;

       НаборЗаписейПерерасчета.Записать();

   КонецЦикла;

КонецПроцедуры [360]

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

Запустим 1С:Предприятие и проверим, как выполняется перерасчет записей регистра расчета.

Отменим проведение всех документов "Начисления сотрудникам" и проведем документ Начисления сотрудникам №1 и затем №2. Сформируем отчет "Начисления сотрудникам" (здесь и далее колонки отчета с 4 по 6 скрыты, в целях экономии места):



Теперь откроем документ Начисления сотрудникам №1, изменим оклад Гусакова на 10 000 и проведем документ. В отчете [361] "НачисленияСотрудникам" нажмем кнопку "Перерасчитать". Будет выполнен перерасчет начисления премии Гусакову и Деловому:



Результат работы отчета будет содержать новые значения премии Гусакова:



И, наконец, проведем документ Начисления сотрудникам №3 и нажмем "Перерасчет" в отчете "НачисленияСотрудникам". Снова будет произведен перерасчет оклада и премии Гусакова:

[362]

А данные отчета будут содержать актуальные значения начисления оклада и премии:



[363]


XML-сериализация


Термином XML-сериализация обозначается механизм, позволяющий представить объект 1С:Предприятия в виде последовательности данных в формате XML. Кроме этого XML-сериализация позволяет выполнить и обратное преобразование – представить последовательность данных формата XML в виде объекта 1С:Предприятия, если существует подходящий тип данных.

Дело в том, что объект обмена, являющийся в системе 1С:Предприятие единым целым, на самом деле представляет собой совокупность данных различных типов, определенным образом связанных между собой. Например, элемент справочника может содержать, кроме кода и наименования, некоторое количество реквизитов различного типа и некоторое количество табличных [398] частей, содержащих в свою очередь некоторое количество реквизитов различного типа. В результате XML-сериализации вся эта совокупность данных представляется в виде последовательности соответствующих данных формата XML. B результате обратного преобразования производится "сборка" объекта, при условии, что существует подходящий тип данных 1С:Предприятия.



Зачем нужен периодический регистр сведений?


Начнем мы с того, что обратим ваше внимание на документ "ОказаниеУслуги". Как вы помните, в этом документе мы выбираем услугу, которая оказывается, и затем указываем цену.

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

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

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

Поэтому для хранения стоимости услуг мы используем новый пока еще для нас объект – регистр сведений. [119]



Зачем нужен план видов расчета и регистр расчета


В этой главе мы рассмотрим возможности системы 1С:Предприятие, которые она предоставляет для автоматизации сложных периодических расчетов.

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

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

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

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

Во-первых, это может быть влияние на исходные данные, используемые при расчете. В качестве примера можно привести начисление премии в виде процента от оплаты по окладу. При изменении оплаты по окладу размер премии тоже должен быть пересчитан, исходя из новой суммы начисленного оклада. Другими словами, сумма начисленного оклада является базой для расчета премии. Причем, поскольку оклад рассчитывается за некоторый период, то при расчете премии нам интересно знать не значение [318] оклада вообще, а сумму, которая начислена в том периоде, который влияет на расчет премии. Такой период мы будем называть базовым, а подобную зависимость между видами расчета мы будем называть зависимостью по базовому периоду.




В качестве примера рассмотрим начисление премии за апрель. Премия должна начисляться в размере 10% от суммы, начисленной в качестве оплаты по окладу. Следовательно, необходимо проанализировать все записи о начислениях оплаты по окладу, которые попадают в интересующий нас базовый период, а именно апрель. Допустим, общая сумма таких начислений составила 8000 рублей – в этом случае премия должна быть начислена в размере 800 рублей:

Зависимость премии от оклада по базовому периоду



Во-вторых, это влияние может быть не на исходные данные, а на сам период, за который производится расчет. В качестве примера можно привести расчет оплаты по окладу и невыход на работу. Предположим, что мы начислили сотруднику оплату по окладу за март месяц. В этом случае период действия такого расчета будет с 01.03.2004 по 31.03.2004. После этого мы получили информацию от руководителя отдела, что, оказывается, сотрудник отсутствовал на работе с 1 по 10 марта по неизвестной причине. В этом случае нам нужно будет произвести расчет "Невыход" (в котором можно рассчитать какие-то удержания с сотрудника). Но кроме этого, нам [319] нужно будет пересчитать и оклад сотрудника, исходя из того, что фактический период действия расчета "Оклад" стал теперь с 11.03.2004 по 31.03.2004. Такое влияние мы будем называть вытеснением по периоду действия. В результате, если за полный месяц работы сотруднику должно было быть начислено 9300 рублей то теперь, за фактический период работы начисление составит 6300 рублей:



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

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

Фактический период – это то, что получилось из периода действия после анализа всех периодов действия расчетов, которые вытесняют наш по периоду действия. [320]



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

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

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

В системе 1С:Предприятие такой универсальный механизм реализован при помощи планов видов расчета и регистров расчета. И первым объектом конфигурации, с которым мы начнем знакомиться в этой главе, будет План видов расчета. [321]


Зачем нужно проведение документа по нескольким регистрам?


До сих пор мы с вами учитывали только количественное движение материалов в OOO "На все руки мастер". Для этих целей мы создали регистр накопления "ОстаткиМатериалов". Однако, как вы, наверное, догадываетесь, одного только количественного учета совершенно недостаточно для нужд нашего ООО.

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

После того, как мы начали автоматизировать наше предприятие, руководство ООО "На все руки мастер" высказало пожелание, чтобы весь суммовой учет материалов велся бы теперь по средней стоимости. To есть, при закупке материалов они должны учитываться в ценах приобретения, а при расходе – по средней стоимости, которая рассчитывается исходя из общей суммы закупок данного материала и общего количества этого материала, находящегося в OOO.

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

Таким образом, документы "ПриходнаяНакладная" и "ОказаниеУслуги" должны будут создавать движения не только в регистре "ОстаткиМатериалов", но, одновременно, и в регистре "СтоимостьМатериалов", отражая изменения суммового учета. [139]



Зачем нужно создавать еще один регистр


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

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

·какие именно услуги были оказаны (чтобы составить рейтинг услуг)
·какому именно клиенту оказывались услуги (чтобы предоставить ему скидку от объема оплаченных ранее услуг, например)
·какой мастер предоставлял услуги (чтобы начислить ему заработную плату)

Очевидно, что существующие регистры накопления совершенно не подходят для решения таких задач. Поэтому мы создадим еще одно "хранилище" данных, которое будет использоваться в нашей программе – оборотный регистр накопления "Продажи". [153]



Зачем нужны подсистемы?


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

Для описания такой структуры существуют объекты конфигурации Подсистема. Эти объекты располагаются в ветке объектов "Общие" и позволяют строить древовидную структуру, состоящую из подсистем и подчиненных подсистем.

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

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

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

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



Запись/чтение документов XML


В отличие от XML-сериализации, механизмы записи/чтения документов XML позволяют работать с данными формата XML на "базовом" уровне, без привязки к объектам 1С:Предприятия. В частности они позволяют открывать файлы XML для чтения, читать данные из файлов, создавать новые файлы XML и записывать в них данные. [399]