Делаем отчет FastReport интерактивным

Описание задачи: 

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

Описание решения: 

Специалистами компании "Бизнес технологии" был реализован следующий функционал:

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

Вывод просроченных задач в гриде "все задачи"

Описание задачи: 

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

Описание решения: 

Вот как я решил эту проблему:

1. В сервисе sq_Task в ветке where - TaskPeriods - TaskDate выставил логические операторы OR
2. В наборе фильтров TaskDate создал еще два набора фильтров TaskDateNew, с логическим оператором AND, и OverDue - с логическим оператором AND
3. В наборе фильтров TaskDateNew я создал фильтр сравнения TaskStartLessThanToDate с условием tbl_Task.StartDate < Parameter: ToDate
4. В наборе фильтров TaskDateNew создал еще один набор фильтров TaskEndFilter - с логическим оператором OR
5. В наборе фильтров TaskEndFilter создал фильтр сравнения TaskDueDateMoreThanStartDate c условием: tbl_Task.DueDate >= Parametr: StartDate
6. Теперь выводим просроченные задачи. В наборе фильтров TaskDate создаем набор фильтров OverdueTasks с логическим оператором AND
7. В наборе фильтров OverdueTasks создаем 2 фильтра сравнения: TimeOverdue (с условием - tbl_Task.DueDate < Parameter: CurrentDay) и NotFinished ( с условием tbl_Task.IsFinish < > Parameter: IsFinish)

Простая манипуляция, но может кому-то пригодится :)

snimok-2011-12-05_141419.png157.24 кб

Создание нового справочника

Описание задачи: 

Необходимо добавить новый справочник в систему.

Описание решения: 

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

1. Создать группу сервисов в нужном модуле системы, например, в группе Common\Dictionaries.

2. В созданной группе создать таблицу справочника, указать название, например, tbl_ContactPreference. Указать значение свойства «Группа таблиц» = «Все справочники». Создать в таблице поля Name и Description типа строка Unicode, указать заголовки полей. Сохранить и закрыть сервис таблицы.

3. В созданной группе создать запрос sq_ContactPreference, указать From Table = tbl_ContactPreference добавить колонки ID, Name, Description.

Добавить параметр типа «Уникальный идентификатор» с именем ID. Добавить фильтр сравнения с кодом ID и выражением сравнения: в левой части tbl_ContactPreference.ID, в правой параметр ID. Выключить фильтр.

4. Создать сервис набора данных (Dataset) с кодом ds_ContactPreference и заголовком «Предпочтения контакта». Указать запрос на выборку равным sq_ContactPreference. Добавить 3 поля типа Строка – ID, Name, Description. Для поля Name установить признаки «Поле для поиска», «Поле для отображения», для поля Description установить признак «Поле для отображения». Для набора данных установить свойства «Ключевое поле» = ID, Первичное поле для отображения = Name.

5. Зарегистрировать справочник в системе. Для этого запустить клиентское приложение TSCRM.exe, выбрать пункт меню «Файл» - «Справочники» - «Настройка справочников», выбрать нужный пункт списка (например, [Общие справочники]), нажать [Добавить справочник]. Указать значения «Заголовок» = «Предпочтения контактов», «Источник данных» = «Предпочтения контакта».

Для простых справочников с составом полей ID, Name, Description указывать окно редактирования не нужно, будет использовано стандартное окно для таких справочников (wnd_SingleFieldEdit). В случае если Ваш справочник содержит иной состав полей, необходимо создать для него сервис окна редактирования и указать созданное окно в поле «Окно редактирования» карточки регистрации справочника.

6. Перезапустить клиентское приложение, убедиться в наличии и работоспособности справочника.

Данные операции можно осуществить автоматически. В меню «Файл» - «Справочники» - «Настройка справочников» выбрать пункт [Создать справочник], указать необходимые значения и состав полей. В результате требуемый справочник будет создан и зарегистрирован в системе. За подробной инструкцией по использованию механизма автоматизированного создания справочников см. документ «Руководство администратора».

Метки записи:

Интеграция с 1С 8.1 посредством соединения с SQL

Описание задачи: 

Перенос справочников Номенклатуры с ценами и сопутствующими справочниками и контрагентов со всеми сопутствующими данными и справочниками.

Описание решения: 

Инструкция в прикрепленном файле.

Тем кого заинтересует могу выслать тестовый вариант на е-мейл.

Обработка универсальна и также может быть переделана под нетиповые конфигурации 1С и базы данных Terrasoft.

Инструкция по использованию обработки.doc261.5 кб

Локализация (поддержка нескольких языков)

Описание задачи: 

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

Описание решения: 

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

  • перевести заголовки сервисов DBDataset и полей в нем
  • перевести заголовки окон, кнопок и других компонентов, не связанных с DBDataset-ами

Идея решения такова:

  • строковые константы будут храниться в глобальном объекте приложения
  • для локализации датасетов и окон будут вызываться соответствующие функции

Итак, первый шаг - добавляем в конфигурацию новый сервис-скрипт, в котором определяем функцию инициализации:

function LocalInit() {
        if (Connector.Attributes('LangResource') == null)
        {
                Connector.Attributes('LangResource') = new Object();
                var lr = Connector.Attributes('LangResource');
                lr.Language = GetRegParamValue(REG_SZ, HKEY_CURRENT_USER, '<путь в реестре>', 'Language');
                if ((lr.Language == null) || (lr.Language == ''))
                        lr.Language = 'RUS';

        }
        else
                var lr = Connector.Attributes('LangResource');
               
        if (lr.Language == 'ENG')
        {
                //-- Common
                lr['Common.btnOK'] = 'OK';
                lr['Common.btnCancel'] = 'Cancel';
                lr['Common.btnAdd'] = 'Add';
                //...
               
                lr['DatasetCommon.CreatedByUserName'] = 'Author';
                lr['DatasetCommon.CreatedByID'] = 'Author';
                //...

                //-- wnd_PVCNotifyEdit
                lr['wnd_PVCNotifyEdit.WindowCaption'] = 'Message';
                lr['wnd_PVCNotifyEdit.edtText'] = 'Text';
                lr['wnd_PVCNotifyEdit.rbCode'] = 'Code';
                lr['ds_PVCNotify.Message'] = 'Message';
                lr['ds_PVCNotify.IsProcessed'] = 'Is Processed';
                lr['ds_PVCNotify.ForUserName'] = 'Receiver';

                //-- ds_PVCServiceLock
                lr['ds_PVCServiceLock.IsActive'] = 'Active';
                lr['ds_PVCServiceLock.CreatedOn'] = 'Start';
                lr['ds_PVCServiceLock.ModifiedOn'] = 'Finish';
                //...
                               
        }
        else
        {
                //-- Common
                lr['Common.btnOK'] = 'ОК';
                lr['Common.btnCancel'] = 'Отмена';
                lr['Common.btnAdd'] = 'Добавить';
                //...
               
                lr['DatasetCommon.CreatedByUserName'] = 'Автор';
                lr['DatasetCommon.CreatedByID'] = 'Автор';
                //...

                //-- wnd_PVCNotifyEdit
                lr['wnd_PVCNotifyEdit.WindowCaption'] = 'Сообщение';
                lr['wnd_PVCNotifyEdit.edtText'] = 'Текст';
                lr['wnd_PVCNotifyEdit.rbCode'] = 'Код';
                lr['ds_PVCNotify.Message'] = 'Сообщение';
                lr['ds_PVCNotify.IsProcessed'] = 'Обработано';
                lr['ds_PVCNotify.ForUserName'] = 'Получатель';

                //-- ds_PVCServiceLock
                lr['ds_PVCServiceLock.IsActive'] = 'Блокировка активна';
                lr['ds_PVCServiceLock.CreatedOn'] = 'Начало';
                lr['ds_PVCServiceLock.ModifiedOn'] = 'Окончание';
                //...
        }
}

Функция создает объект LangResource (который будет храниться в атрибутах объекта Connector), и загружает в него строковые константы в зависимости от языка интерфейса. Обратите внимание на то, что название констант состоит из двух частей, разделенных точкой. Первая часть - код окна/датасета, вторая - код компонента/поля. Кроме того, общие константы имеют префикс Common и CommonDataset соответственно.

Далее, в этом же скрипте определяем функции для локализации окна и датасета:

function SetWindowLanguage(Window) {
        var lr = Connector.Attributes('PVCLangResource');
        if (lr[Window.Caption + '.WindowCaption'] != null)
                Window.WindowCaption = lr[Window.Caption + '.WindowCaption'];
               
        for (var i = 0; i < Window.ComponentCount; i++)
        {
                var c = Window.Components(i);
               
                if (lr['Common.' + c.Name] != null)
                        c.Caption = lr['Common.' + c.Name];
               
                if (lr[Window.Caption + '.' + c.Name] != null)
                        c.Caption = lr[Window.Caption + '.' + c.Name];
        }      

        for (var i = 0; i < Window.NonVisualComponentCount; i++)
        {
                var c = Window.NonVisualComponents(i);
               
                if (lr['Common.' + c.Name] != null)
                        c.Caption = lr['Common.' + c.Name];
               
                if (lr[Window.Caption + '.' + c.Name] != null)
                        c.Caption = lr[Window.Caption + '.' + c.Name];
        }      
}

function SetDatasetLanguage(Dataset) {
        var lr = Connector.Attributes('PVCLangResource');
        if (lr[Dataset.Code + '.DatasetCaption'] != null)
                Dataset.Caption = lr[Dataset.Code + '.DatasetCaption'];
               
        for (var i = 0; i < Dataset.DataFields.Count; i++)
        {
                var c = Dataset.DataFields.Items(i);
                if (lr['DatasetCommon.' + c.Name] != null)
                        c.Caption = lr['DatasetCommon.' + c.Name];
               
                var u = ExtractUSICodeEx(Dataset.USI);
                if (lr[u + '.' + c.Name] != null)
                        c.Caption = lr[u + '.' + c.Name];
        }      
}

Эти функции получают на входе ссылку на окно/датасет, и используя глобальный объект LangResource, заменяют заголовки.

Финальный штрих - в скрипте scr_Main надо добавить использование нашего скрипта и вызов функции LocalInit, а в скриптах scr_BaseEdit, scr_BaseDBEdit, scr_BaseGridArea и scr_BaseWorkspace - использование скрипта и вызов функций SetWindowLanguage и SetDatabaseLanguage

Интеграция с сайтом (БД MS SQL SERVER 2005)

Описание задачи: 

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

Описание решения: 

Для реализации функционала был создан на сервере job + процедура, написанная на С#, которая забирала данные с сайта.
Текст процедуры

using System;
using System.Data;
using System.Text;
using System.Net;
using System.IO;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Collections.Generic;
using Microsoft.SqlServer.Server;

public partial class StoredProcedures
{
    public static SqlInt32 GetXMLFromSite(SqlString WebSiteURL, out SqlString XMLString)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create((String)WebSiteURL);
        XMLString = "";
        try
        {
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream receiveStream = response.GetResponseStream();
            StreamReader readStream = new StreamReader(receiveStream, Encoding.);
            XMLString = (SqlString)readStream.ReadToEnd();
            response.Close();
            readStream.Close();
        }
        catch (Exception e)
        {
            return -1;
        }
        finally
        {
        }
        return 0;
    }
}

Метки записи:

Дерево: пример ограниченного дерева

Описание задачи: 

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

Описание решения: 

Решение получилось довольно простым.
Создается копия датасета дерева, с ограничением ParentID = null. Все действия производятся над этим датасетом. Датасет дерева на BeforeOpen считывает результат этого датасета, и заполняет IncludeFilter.

Множественный выбор из справочника

Описание задачи: 

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

Описание решения: 

Создаем в окне LookupControl и DatasetLink. В свойстве Dataset DatasetLink'а выбираем сервис набора данных, из которого хотим делать выбор. В свойстве LookupDatasetLink LookupControl'а указываем созданный DatasetLink, в свойстве SelectWindowUSI указываем wnd_MultiSelectData. Создаем обработчики события окна OnNotify и события LookupControl'а OnPrepareSelectWindow со следующим наполнением (пример на основе справочника "Ответственные"):

function LookupControlOnPrepareMultiSelectWindow(LookupControl, SelectWindow,
                DisplayFieldName, SearchFieldName, SearchFieldNames) {
        if (IsEmptyValue(DisplayFieldName)) {
                DisplayFieldName = 'Name';
        };     
        if (IsEmptyValue(SearchFieldName)) {
                SearchFieldName = 'Name';
        };
        if (IsEmptyValue(SearchFieldNames)) {
                SearchFieldNames =  'Name';
        };
       
    var KeyFieldName = 'ID';
   
    var KeyValues = System.CreateObject('TSObjectLibrary.StringsList');
    var StoredValues = new String(LookupControl.Value);
    if (!IsEmptyValue(LookupControl.Value)) {
        var StoredValuesArray = StoredValues.split(',');
            for (var i = 0; i < StoredValuesArray.length; i++) {
                KeyValues.Add(StoredValuesArray[i]);
            }
        }
    var DisplayFieldValues = System.CreateObject('TSObjectLibrary.StringsList');
    var StoredDisplayValues = new String(LookupControl.Text);
    if (!IsEmptyValue(LookupControl.Text)) {
        var StoredDisplayValuesArray = StoredDisplayValues.split(',');
            for (var i = 0; i < StoredDisplayValuesArray.length; i++) {
                DisplayFieldValues.Add(StoredDisplayValuesArray[i]);
            }
        }
    SetAttribute(SelectWindow, 'KeyValues', KeyValues);
    SetAttribute(SelectWindow, 'DisplayFieldValues', DisplayFieldValues);    
    SetAttribute(SelectWindow, 'LookupControl', LookupControl.Name);
    SetAttribute(SelectWindow, 'SearchFieldNames', SearchFieldNames);
    SetAttribute(SelectWindow, 'SearchFieldName', SearchFieldName);
    SetAttribute(SelectWindow, 'DisplayFieldName', DisplayFieldName);
    SetAttribute(SelectWindow, 'KeyFieldName', KeyFieldName);
    SetAttribute(SelectWindow, 'NotifyObject', LookupControl.ParentWindow);
    SetAttribute(SelectWindow, 'NotifyWindow', LookupControl.ParentWindow);    
}

function MultiSelectOnNotify(ScriptableService, Sender, Message, Data, LookupControl) {
        if ((Message == MSG_OK) && (Sender != null) &&
                (Sender.Name == 'wnd_MultiSelectData')) {
            if (Sender.Attributes('LookupControl') == LookupControl.Name) {
                var KeyValues = GetAttribute(Sender, 'KeyValues');
                var DisplayFieldValues = GetAttribute(Sender, 'DisplayFieldValues');
                LookupControl.Value = KeyValues.CommaText;
                LookupControl.Text = DisplayFieldValues.CommaText;
            };
    };
}

function wnd_TestMultiLookupOnNotify(ScriptableService, Sender, Message, Data) {
        MultiSelectOnNotify(ScriptableService, Sender, Message, Data, edtOwnerIDs);
}

function edtOwnerIDsOnPrepareSelectWindow(LookupControl, SelectWindow) {
        var DisplayFieldName = 'Name';
        var SearchFieldName = 'Name';
        var SearchFieldNames = 'Name';
        LookupControlOnPrepareMultiSelectWindow(LookupControl, SelectWindow,
                DisplayFieldName, SearchFieldName, SearchFieldNames)
}

TestMultiLookup.rar2.47 кб

Динамические пользовательские фильтры

Описание задачи: 

Задача заключалась в том чтобы добавить в раздел "Документы" пользовательские фильтры - все поля раздела "Продукты" (получилось бы что-то около 30 пользовательских фильтров)

Описание решения: 

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

function Main(){
        var OfferingDataset = Services.GetNewItemByUSI('ds_Offering');
        var DocumentSelectQuery = Services.GetNewItemByUSI('sq_Document');
        DocumentSelectQuery.Items(0).Filters
        for (var i = 0; i < OfferingDataset.DataFields.Count; i++){
                var DataField = OfferingDataset.DataFields.Items(i);
                if((DataField.FieldType == dftCalc)||(DataField.FieldType == dftBlob)){
                        continue;
                }
                var ExistsFilter = DocumentSelectQuery.Items(0).Filters.CreateExistsFilter();
                ExistsFilter.IsEnabled = false;
                var Code = 'Plane' + DataField.Name;
                ExistsFilter.Code = Code;
                DocumentSelectQuery.Items(0).Filters.Add(ExistsFilter);
                var SelectQuery = ExistsFilter.TestExpression.ExpressionSelectQuery;
                var SqItem = SelectQuery.CreateItem()
                var OfferingTable = Services.GetNewItemByUSI('tbl_Offering');
                SqItem.FromTable = OfferingTable
                SqItem.FromTableAlias = 'PlaneOffering'
                AddGeneralColumnByFromTableFieldName(SqItem, 'ID', 'ID', true, false)
                var Field1 = OfferingTable.Fields.ItemsByName('ID');
                var Field2 = DocumentSelectQuery.Items(0).FromTable.Fields.ItemsByName('PlaneOfferingID')
                var Filters = SqItem.Filters;
                AddQueryCompareFilter1(Filters, 'PlaneOfferingID',
                        Field1, SqItem.FromTableAlias ,Field2 , 'tbl_Document', cotEqual);      
               
                SelectQuery.Add(SqItem);
                       
                var Columns = OfferingDataset.SelectQuery.Items(0).Columns;
                var SQLName = Columns.ItemsByAlias(DataField.Name).Field.SQLName;
                var Field1 = OfferingTable.Fields.ItemsByName(SQLName);
                AddQueryUserFilter(DataField, Filters, Code, Field1);  
        }
        DocumentSelectQuery.UID = Connector.GenGUID();
        DocumentSelectQuery.USI = "Documents\\General\\Main Grid\\sq_DocumentCopy";
        Services.SaveItem(DocumentSelectQuery,1);
}

function AddQueryUserFilter(DataField, Filters, Code, Field1) {
        var DataFieldType = DataField.FieldType;
        switch (DataFieldType) {
                case (dftString):
                        var CompareFilter = Filters.CreateStringUserFilter();
                        Filters.Add(CompareFilter);
                break;
                case (dftInteger):
                        var CompareFilter = Filters.CreateIntegerUserFilter();
                        Filters.Add(CompareFilter);
                break;
                case (dftFloat):                       
                        var CompareFilter = Filters.CreateFloatUserFilter();
                        Filters.Add(CompareFilter);
                break;
                case (dftBool):
                        var CompareFilter = Filters.CreateBoolUserFilter();
                        Filters.Add(CompareFilter);
                break;                                                 
                case (dftDateTime):
                        var CompareFilter = Filters.CreateDateTimeUserFilter();
                        Filters.Add(CompareFilter);
                break;
                case (dftBlob):
                        return;
                break;                         
                case (dftLookup):
                        var CompareFilter = Filters.CreateLookupUserFilter();
                        CompareFilter.LookupDataset = DataField.LookupDataset;
                        Filters.Add(CompareFilter);
                break;
                case (dftEnum):
                        var CompareFilter = Filters.CreateEnumUserFilter();
                        CompareFilter.Enum = DataField.Enum;
                        Filters.Add(CompareFilter);
                break;
        }
        CompareFilter.Code = Code;
        CompareFilter.Caption = DataField.Caption
        CompareFilter.DisplayGroupName = "Плоскость";
        var TestExpression = CompareFilter.CreateFieldFilterExpression();
        TestExpression.Field = Field1;
        TestExpression.TableAlias = 'PlaneOffering';
        CompareFilter.TestExpression = TestExpression;
        return CompareFilter;
}

function AddQueryCompareFilter1(Filters, Code, Field1, TableAlias1, Field2, TableAlias2,
        CompareOperatorType) {
        var CompareFilter = Filters.CreateCompareFilter();
        Filters.Add(CompareFilter);
        CompareFilter.Code = Code;
        CompareFilter.CompareOperator = CompareOperatorType;
        var TestExpression = CompareFilter.CreateFieldFilterExpression();
        var ValueExpression = CompareFilter.CreateFieldFilterExpression();
        TestExpression.Field = Field1;
        if (!IsEmptyValue(TableAlias1)) {
                TestExpression.TableAlias = TableAlias1;
        }      
        ValueExpression.Field = Field2;
        if (!IsEmptyValue(TableAlias2)) {
                ValueExpression.TableAlias = TableAlias2;
        }
        CompareFilter.TestExpression = TestExpression;
        CompareFilter.ValueExpression = ValueExpression;
        return CompareFilter;
}

Пользовательские поля

Описание задачи: 

Необходимо, чтобы в основном реестре раздела "Задачи" отражались поля прикреплённого к нему продукта (связь один к одному), т.е. в случае, если пользователь когда-нибудь в будущем создаст пользовательское поле в разделе "Продукты", это поле должно попасть в раздел "Задачи".

Описание решения: 

Оказалось всё просто:
в сервисе uf_Offerings добавил ds_Task и
wnd_TasksGridArea(grd_Data)соответственно для датасета и окна основного грида раздела "Задачи".
Однако, столкнулся с одной проблемой:
при добавлении полей в SelectQuery раздела "Задачи" поле привязывается к основной таблице SelectQuery, и для справочников таблицы джойнятся на основную таблицу.
Пришлось дописывать базовую логику скрипта scr_WndUserFieldsEdit

function AfterAddItem(Window) {
.....
  try {
       if (IsApplyForAllTypes) {
         ApplyForAllTypes(Item);
    }
      ....
       UpdateItemsButtons();
    <em>ChengeOfferingFields(Window, Item, wndUserFieldsEdit.SelectedUserFields.Table.SQLName);       </em>
    } catch (E) {
      CatchException(E);
  }
}

function ChengeOfferingFields(Window, Item, TableName){
  if(TableName == 'tbl_Offering'){
    var SelectQuery = Services.GetNewItemByUSI('sq_Tasks');
    var Column = SelectQuery.Items(0).Columns.ItemsByAlias(Item.Name);
    Column.DependsOn.Add('tbl_Offering');
    var Table = Services.GetSingleItemByUSI('tbl_Offering');
    var TableFields = Table.Fields;
    Column.Field =TableFields.ItemsByName(Item.Name);
    Column.ParentJoin =
                        SelectQuery.Items(0).Joins.ItemsByLeftTableAlias('tbl_Offering');
                Services.SaveItem(SelectQuery, 1);      
                if(Item.ItemType == 6){
                        SelectQuery.Items(0).Joins.Items(SelectQuery.Items(0).Joins.Count - 1).ParentJoin
                                = SelectQuery.Items(0).Joins.ItemsByLeftTableAlias('tbl_Offering');            
                }      
        }
}

Смотрим структуру сервиса

Описание задачи: 

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

Описание решения: 

Как известно, программисты люди большей частью ленивые и нередко думают над тем, как бы сделать это так, чтобы потом ничего не делать. Заглянув в таблицу tbl_Service я не нашел для себя ничего удивительного - вот они, сервисы. Только формат, в котором они хранятся не структурирован.

Ну, что ж делать - лень двигатель прогресса. Пришлось вспоминать азы программирования в среде Visual Studio, но вскоре, я все-таки сделал то, что хотел.

Вуаля - структура как на ладони.

Раздел с набором всех сервисов

Раздел с набором всех сервисов

Структура сервиса

Структура сервиса

Выделение полей "только для чтения" цветом или IsEnabled

Описание задачи: 

В карточке редактирования wnd_...Edit предлагается выделять Caption визуальных контролов для полей Dataset-а с признаком "Только для чтения" цветом, отличающимся от цвета остальных полей, например, зеленым, или выставлять IsEnabled = false - чтобы пользователи не путались в какое поле можно вводить данные.

Описание решения: 

   //Модуль scr_BaseDBEditUtils  
 
function ProcessBaseDBEditOnPrepare(Window, BaseDBEdit) {
   ...     /* Добавляем строчку в функцию... */
    SetCaptionColorForIsReadOnlyFields(Window); //Устанавливает цвет для полей с признаком "Только чтение";
}
 
//Устанавливает цвет для полей с признаком "Только чтение";
function SetCaptionColorForIsReadOnlyFields(Window) {
    for (var i = 0; i < Window.ComponentCount; i++) {
        var DataField = Window.Components(i).DataField;
        if(!IsUndefined(DataField)) {
            if (DataField.IsReadOnly) {
                //Можно включить выделение цветом
                var Component = Window.Components(i);
                Component.CaptionColor = clReadOnlyCaptionColor;
                Component.IsEnabled = false;
            }
        }
    }
}  

Метки записи:

Вывод суммы прописью в отчете

Описание задачи: 

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

Описание решения: 

Для выполнения задачи вывода суммы прописью можно воспользоваться скриптом scr_ConvertUtils, функцией AmountToStr(), пример вызова которой представлен ниже: 

function Main() {
   var MyNumber = 3.62;
   var MyNumberStr = MyNumber.toString();
   Log.Write(1, 'Сумма прописью: ' + AmountToStr(MyNumberStr, 'ru', 'RUR'));
}

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

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

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

function dsMyReportOnCalcFields(Dataset) {

   var Value = Dataset.ValAsFloat('MyFloatField');
   var ValueStr = Value.toString();

   ValueStrRUR = AmountToStr(ValueStr, 'ru', 'RUR'));

   Dataset.Values('MyFieldStr') = ValueStrRUR;
}

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

Метки записи:

Создание нового отчета FastReport

Описание задачи: 

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

Описание решения: 

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

В модуле [Common] создать группу [Reports]

  1. В группе [Common \ Reports] создать группу [Cities]
  2. В группе [Common \ Reports \ Cities] создать группу [Cities by Country]

Создание выборки данных [Select Query]

  1. В созданной группе создать запрос на выборку [sq_ReportCitiesByCountry]
  2. Для запроса указать код [sq_ReportCitiesByCountry]
  3. В выражении [FROM] указать таблицу [tbl_City]
  4. Используя правую кнопку мыши, создать новый JOIN (присоединенную таблицу). Указать тип присоединения [LEFT OUTER JOIN], таблицу [tbl_Country] по условию ON [tbl_Country.ID] = [tbl_City.CountryID].
  5. Определить колонки (правой кнопкой на узле [Select] – контекстное меню «Основная колонка»)
    • [tbl_City].[Name] как [CityName]
    • [tbl_Country].[Name] как [CountryName]
  6. Для колонки [tbl_Country].[Name] указать тип сортировки [По возрастанию]
  7. Сохранить сервис.

Создание набора данных [Dataset]

  1. В группе [Common \ Reports \ Cities \ Cities by Country] создать источник данных [ds_ReportCitiesByCountry]
  2. Для набора данных указать код [ds_ReportCitiesByCountry]
  3. Для источника данных [ds_ReportCitiesByCountry] задать запрос на выборку [sq_ReportCitiesByCountry]
  4. Для источника данных [ds_ReportCitiesByCountry] определить строковые поля
    • [CityName]
    • CountryName]
  5. Сохранить сервис. 

Создание отчета [FastReport]

  1. В группе [Common \ Reports \ Cities \ Cities by Country] создать отчет (сервис FastReport)Комментарий1 [fr_ReportCitiesByCountry]
  2. Для отчета указать USI [Common\Reports\Cities\Cities by Country\fr_ReportCitiesByCountry].
  3. Установить заголовок отчета [Caption] в [Города в разрезе стран]
  4. Добавить в отчет источник данных – на вкладке «Data» в меню «TS Components» - «TS Dataset»
  5. В свойстве [Name] источника данных отчета указать [ds_ReportCitiesByCountry]
  6. В свойстве [USI] источника данных указать ссылку на сервис набора данных [ds_ReportCitiesByCountry]
  7. Добавить в созданный отчет полосу (Band) типа [Group Header] указав ему поле для группировки [ds_ReportCitiesByCountry]. [ContryName] (в меню «Insert Band» - «Group Header»).
  8. Добавить в созданный [Band] поле [ds_ReportCitiesByCountry]. [CountryName] перемещением курсором (Drag&Drop) поля из закладки Data. Комментарий2
  9. Добавить в созданный отчет [Band] типа [MasterData] указав ему набор данных [ds_ReportCitiesByCountry]
  10. Добавить в созданный [Band] поле [ds_ReportCitiesByCountry]. [CityName] перемещением поля из закладки Data.
  11. Сохранить построенный отчет
  12. Заполнить данными справочник городов (если данные отсутствуют).
    • Запустить TSCRM.exe
    • В меню «Файл» - «Справочники» - «Общие справочники» - «Города» вызвать справочник городов.
    • Заполнить справочник несколькими записями.
  13. Запустить предварительный просмотр отчета в окне дизайнера отчета. 

Регистрация отчета в разделе

  1. Зайти в систему TSCRM в раздел [Анализ] - [Отчеты]
  2. Добавить новый отчет Fast Report
  3. В форме добавления отчета ввести соответствующие поля:
    • Сервис отчета – выбрать отчет [Города в разрезе стран]. Поле «Название» заполняется автоматически.
    • Тип фильтрации – «Для всех записей»
    • Тип – для всех записей
    • Фильтруемый источник данных – «ds_ReportCitiesByCountry»
    • Окно фильтрации – оставляем пустым
    • Раздел – [Контакты]
  4. Выйти и зайти в систему в раздел [Контакты].

Запустить отчет (меню «Отчеты» - «Города в разрезе стран»).

Метки записи:

Создание новой детали в разделе

Описание задачи: 

В разделе необходимо создать дополнительную деталь.

Описание решения: 

В качестве примера создания детали продемонстрируем создание детали [Регистрационные документы] в разделе [Контакты]. Данная деталь предназначена для хранения информации о паспортных данных и прочих документах контакта. 

Для создания детали необходимо выполнить следующие шаги: 

1. В модуле Contacts найти группу Details. Создать в ней группу сервисов RegDocuments. 

2. В созданной группе создать таблицу tbl_ContactRegDoc. Добавить поля ContactID (тип «Уникальный идентификатор»), Title (тип Строка Unicode), RegDate (тип Дата), Description (тип Строка Unicode). В списке Relations создать новый внешний ключ, указать поле первичного ключа – tbl_Contact.ID, вторичный ключ – ContactID. Сохранить и закрыть сервис таблицы. 

3. В созданной группе создать запрос sq_ContactRegDoc. Указать From Table = tbl_ContactRegDoc, создать колонки ID, ContactID, Title, RegDate, Description. Создать параметры ID и ContactID (оба с типом «Уникальный идентификатор»). Создать два выключенных фильтра сравнения, первый с кодом ID и выражением tbl_ContactRegDoc.ID = параметр ID, второй с кодом ContactID и выражением tbl_ContactRegDoc.ContactID = параметр ContactID. 

4. Создать набор данных ds_ContactRegDoc, указать запрос на выборку sq_ContactRegDoc, создать поля ID, ContactID, Title, Description (тип Строка) и RegDate (тип Дата), указать соответствующие полям заголовки. Установить ключевое поле набора данных равным ID. 

5. Для реализации окна реестра детали необходимо создать окно с кодом wnd_ContactRegDocGridArea. Указать значение в свойстве TemplateWindowUSI = wnd_BaseGridArea. Сохранить и закрыть сервис окна. Затем открыть сервис окна, в нем появились унаследованные от шаблона wnd_BaseGridArea элементы. 

6. На вкладке «Невизуальные» окна редактирования для компонента dlData в свойстве Dataset установить значение ds_ContactRegDoc. Затем на вкладке «Визуальные» для компонента grdData добавить представление DataGridView, нажать правой кнопкой и выбрать пункт [Определить колонки]. В появившемся списке выбрать поля Title, RegDate, Description. Для компонента grdData установить значение свойства ActiveView равным DataGridView. 

7. Для окна на вкладке «События» стереть существующий обработчик события OnPrepare и с помощью двойного щелчка мыши создать новый. Реализовать следующий код:

function wnd_ContactRegDocsGridAreaOnPrepare(Window) {

     Window.Attributes('EditWindowUSI') = 'wnd_ContactRegDocEdit'

     scr_BaseGridArea.wnd_BaseGridAreaOnPrepare(Window);

}

Сохранить и закрыть скрипт и окно реестра детали.

8. Для реализации окна редактирования детали необходимо создать новое окно с кодом wnd_ContactRegDocEdit. Указать значение в свойстве TemplateWindowUSI = wnd_BaseDBEdit. Сохранить и закрыть сервис окна. Затем открыть сервис окна, в нем появились унаследованные от шаблона wnd_BaseDBEdit элементы. 

9. На вкладке «Невизуальные» окна редактирования для компонента dlData в свойстве Dataset установить значение ds_ContactRegDoc. Затем на вкладке «Визуальные» для компонента frmData добавить компонент TextDataControl, DateTimeDataControl и еще один TextDataControl. Установить имена компонентам edtTitle, edtRegDate и edtDescription соответственно. Задать элементам управления значения свойства DatasetLink значением dlData. Задать элементам управления значения свойства DataFieldName значениями Title, RegDate и Description соответственно. Задать компонентам значение свойства AlignHorizontal равным alhClient, чтобы компоненты заняли все доступное по горизонтали свободное место на окне. 

10. Задать окну значение свойства WindowCaption равным «Регистрационный документ». Сохранить и закрыть сервис окна. 

11. Открыть сервис wnd_ContactsWorkspace. Для компонента pcDetails по аналогии с существующими деталями добавить Page, Frame и WindowContainer. Указать имена компонентов pgContactRegDocDetail, fmContactRegDocDetail и wndContactRegDocDetail соответственно. Для компонента pgContactRegDocDetail указать заголовок «Регистрационные документы». Для компонента wndContactRegDocDetail указать свойство Window = wnd_ContactRegDocsGridArea, а также свойства AlignHorizontal и AlignVertical установить в alhClient и alvClient соответственно. В итоге получаем окно реестра регистрационных документов, встроенное в страницу области деталей раздела.

Сохранить и закрыть сервис окна wnd_ContactsWorkspace.

12. Открыть сервис scr_ContactsWorkspace. В функции RefreshDetails() в конец добавить код по обновлению детали:

function RefreshDetails() {

...

     } else

     if (pcDetails.ActivePage.Name == pgContactRegDocDetail.Name) {

         RefreshCommonDetail(BaseWorkspace, wndContactRegDocDetail, 'ContactID', 'ContactID');

     }

}

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

Метки записи: