NexusSmallErp: РазработкаМодуля1 ...

Glavnaja Stranica | Каталог | Изменения | НовыеКомментарии | Пользователи | Вам запрещён доступРегистрация | Вход:  Пароль:  

Разработка модуля. Часть 1 – Основы


Внимание!
Данная статья описывает процесс разработки с использованием ядра NEXUS версии 2.0 и выше (платформа NEXUS2).

Аннотация

В статье рассматриваются общие принципы создания прикладного модуля на платформе NEXUS2.
Предполагается, что MS SQL Server 2000 Service Pack 3 любой редакции, включая MSDE, установлен локально на вашем компьютере, либо вы имеете к нему доступ в сети, а локально установлена только клиентская часть. Также предполагается, что вы обладаете хотя бы начальным опытом разработчика на MS SQL Server и знаете, чем таблица отличается от хранимой процедуры, а последняя - от триггера :)

Подготовка к работе

Организуем структуру каталогов на вашем компьютере для работы над прикладным модулем. Она может иметь следующий рекомендуемый вид (каталог NEXUS не обязательно корневой на диске):

Структура каталогов для разработки модуля

Для начала работы необходимо также произвести действия по установке компонентов NEXUS:

  1. Загрузить и установить "Проводник" - клиентское приложение NEXUS
  2. Загрузить самораспаковывающиеся архивы перечисленных компонентов системы и разархивировать их в соответствующие каталоги:
    • инсталляционный скрипт ядра NEXUS в каталог NEXUS/Bin (в каталоге будет создан файл Install2000.sql)
    • шаблон для разработки модуля NEXUS (скачайте файл в конце страницы) в каталог NEXUS/Templates
  3. Создать новую базу данных на MS SQL Server и выполнить на ней инсталляционный скрипт ядра NEXUS (Install2000.sql)

Также рекомендуется загрузить архив документации NEXUS в каталог NEXUS/Docs и распаковать его.

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

Для разработки нам понадобится Query Analyser (далее QA)- стандартный инструмент, входящий в поставку MS SQL Server. Однако, если вам привычна другая среда, то вы можете с успехом использовать и её. Единственным требованием к такой среде будет способность поддерживать разработку и трансляцию SQL-скриптов. В качестве примера можно привести как среду VisualStudio, так и профессиональный текстовый редактор MultiEdit.

Описание примера

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

Схема БД для хранения информации о контактах

Рис.1. Схема БД для хранения информация о контактах

В рамках NEXUS понятие "класс" и их наследование поддерживается на уровне базы данных и ядра системы, поэтому, как правило, каждому классу соответствует одноименная таблица для хранения его полей. Связь осуществляется по уникальному идентификатору UDN (Unique Document Number), который является первичным ключом для каждой таблицы класса.

Таком образом, в представленной на рис.1 схеме имеется базовый класс "Контакт" и два подкласса "Компания" и "Персона". Каждая персона может быть связана с одной компанией.

Реализация

Скопируем каталог NEXUS/Templates/ModuleTemplate в NEXUS/Modules. Вначале нам необходимо декларировать таблицы и классы, для которых на следующем этапе будем программировать логику. Эти действия следует описывать в файле _PreInstall.sql. Согласно схеме на рис.1, код будет выглядеть следующим образом

set nocount on

/**
 * Создание таблиц классов
 */
create table Contact (
   UDN                  int not null,
   Phone                varchar(40) null,
   constraint PK_Contact primary key (UDN)
)
go

create table Company (
   UDN                  int not null,
   Fax                  varchar(40) null,
   constraint PK_Company primary key (UDN)
)
go

create table Person (
   UDN                  int not null,
   Mobile               varchar(40) null,
   CompanyUDN           int null,
   constraint PK_Person primary key (UDN)
)
go

/**
 * Декларация классов
 * Подробнее см. документацию по процедуре ObjCreateClass
 */
exec ObjCreateClass
   @Name = 'Contact',  -- системное имя класса
   @Uname = 'Контакт', -- пользовательское имя класса
   @Abstract = 1       -- 1 - абстрактный

exec ObjCreateClass
   @Name = 'Company',
   @Uname = 'Компания',
   @BasedOn = 'Contact' -- имя суперкласса (родительского класса)

exec ObjCreateClass
   @Name = 'Person',
   @Uname = 'Контактное лицо',
   @BasedOn = 'Contact'
go

Небольшое пояснение. Абстрактный документ (класс Doc, таблица Docs), который является корнем иерархии наследования в среде NEXUS, уже имеет 3 поля для хранения данных потомков: Docs.Name и Docs.No типа varchar(128) и Docs.Date типа datetime. Эти поля всегда видны пользователю в правом списке окна проводника. Поэтому мы не создаем поля FullName и Address в таблице Contact, а будем хранить эту информацию в Docs, используя, соответственно, поля Name и No.

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

Рис.2. Классы, созданные разработчиком

Итак, система уже содержит классы, но нам следует подумать и о систематизации их хранения. Для этого создадим в системе иерархию папок: прямо в корневой папке Root у нас будет располагаться папка "Контакты" с подпапками "Компании" и "Контактные лица". Следующий код создает в системе нужные нам папки, нам нужно добавить его в _PreInstall.sql.

declare @SysRootUDN int, @ContactsUDN int, @UDN int

set @SysRootUDN = dbo.nxRootUDNByName('s')
exec DocCreateEnlisted @SysRootUDN, 'Контакты', 'Enlisted', @ContactsUDN output
exec DocCreateEnlisted @ContactsUDN, 'Компании', 'Enlisted', @UDN output
-- запоминаем UDN созданной папки в списке системных корней
-- позднее, мы сможем найти UDN папки по её системному имени
exec nxRootCreate 'ContactCompanies', @UDN, 'Контакты-компании'
exec DocCreateEnlisted @ContactsUDN, 'Контактные лица', 'Enlisted', @UDN output
exec nxRootCreate 'ContactPersons', @UDN, 'Контактные лица'
go

После выполнения этой части скрипта структура папок в проводнике изменится.

Рис.3. Созданная структура папок для хранения информации о контактах

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

Начнем с общего предка наших классов - Contact. Скопируйте файл Sample.sql в ту же папку, где он находится, но под именем Contact.sql (если вы тоже используете FAR, это команда Shift+F5). Откроем файл в QA и начнем редактирование. В файле можно увидеть множество обработчиков событий класса "Sample". Большинство из них нам понадобится, поэтому проще всего будет сразу переименовать все "Sample" на "Contact".

Первым в списке идет обработчик события getp, которое возникает в системе, когда пользователь вызывает в клиентском приложении контекстное меню, щелкая правой кнопкой мыши на выбранном документе. В нем нам нужно просто перечислить все доступные операции с документом, которые будут показаны пользователю (конечно, если он имеет на них соответствующие привилегии, но об этом позже). Такими стандартными операциями являются "Просмотр" и "Редактирование". Для этого вписываем в тело пост-обработчика, который назван просто list_ (подчеркивание означает "пост-", отсутствие - "пре-обработчик", подробнее см. "Руководство разработчика"), следующие команды:

exec nxDropProcedure 'Contact_getp_list_'
go
create procedure Contact_getp_list_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxAddViewProperty 'view', 'Просмотр'
  exec nxAddEditProperty 'edit', 'Редактирование'
end
go

Первый вызов nxDropProcedure обязателен перед созданием каждой новой процедуры: он удаляет процедуру в БД, если она существует. Аналогичный API есть для функций (nxDropFunction), триггеров (nxDropTrigger) и проекций (nxDropView). Поскольку этот вызов нужен всегда, то в следующих приводимых примерах кода процедур мы его пропустим.

Далее, сама процедура - обработчик. Все обработчики принимают три параметра целого типа, через которые ядро передает UDN документов. Как правило, в @p1 содержится UDN документа, с которым произошло данное событие. Это аналог ссылки this или self в ОО-языках.

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

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

create procedure Contact_gets_edit_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFormSetTitle @p1, 'Новый контакт'
  exec nxFormEditString @p1, 'FullName', 'sПолное название', ''
  exec nxFormEditString @p1, 'Address', 'sАдрес', ''
  exec nxFormEditString @p1, 'Phone', 'sТелефон', ''
end
go

Команда nxFormSetTitle задает заголовок окна, в котором пользователь будет создавать документ. Дальше идут объявления редактируемых полей ввода: название, формат (подробнее о них см. главу "Форматы отображения" в "Руководстве разработчика") и начальное значение, если его необходимо задать.

Очевидно, что заполненные пользователем поля, формируемые по gets, нужно сохранить в БД. Для этого существует событие cre. Напишем обработчик Contact_cre_edit_, в параметре @p2 которого ядро передает UDN нового документа.

create procedure Contact_cre_edit_
  @p1 int, @p2 int, @p3 int
as begin
  insert into Contact (UDN, Phone)
    select @p2, dbo.nxFormGetString(@p1, 'Phone')
  update Docs
    set Name = dbo.nxFormGetString(@p1, 'FullName'),
        No   = dbo.nxFormGetString(@p1, 'Address')
    where UDN = @p2
end
go

Функции nxFormGet<Тип> извлекают по идентификатору поля его значение (поля, напомню, были объявлены нами в обработчике события gets). Нам нужно только вставить эти значения в таблицу Contact. Следующим шагом должно быть присвоение значений полям абстрактного документа - предка нашего класса Contact, которые хранятся в таблице Docs и отображаются в клиентском приложении в правой части проводника - списке. В нашем примере поле Name (Название) хранит полное имя контакта, а поле No (номер или дополнительная информация) - его адрес. Следует учитывать, что в момент вызова обработчика ядро уже вставило запись в Docs, поэтому вместо insert используется update.

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

Ранее, в обработчике Contact_getp_list_ мы объявили две операции: edit и view. Это имена обработчиков события read, которые формируют поля документа всякий раз, когда пользователь вызовет, соответственно, редактирование или просмотр документа.

create procedure Contact_put_edit_
  @p1 int, @p2 int, @p3 int
as begin
  update Contact
    set Phone = dbo.nxFormGetString(@p1, 'Phone')
    where UDN = @p1

  update Docs
    set Name = dbo.nxFormGetString(@p1, 'FullName'),
        No   = dbo.nxFormGetString(@p1, 'Address')
    where UDN = @p1
  
  exec nxShortcutSynchronize @p1
end
go

Чтобы передать в процедуру nxFormEditString, начальное значение, мы объявили буферную переменную типа string. Тип string объвляется ядром NEXUS при установке, он соответствует varchar(255).

Аналогично реализованному обработчику события cre нам необходимо реализовать обработчик для события записи данных после редактирования документа. Это событие в системе называется put, а обработчик будет именоваться Contact_put_edit_.

create procedure Contact_put_edit_
  @p1 int, @p2 int, @p3 int
as begin
  update Contact
    set Phone = dbo.nxFormGetString(@p1, 'Phone')
    where UDN = @p1

  update Docs
    set Name = dbo.nxFormGetString(@p1, 'FullName'),
        No   = dbo.nxFormGetString(@p1, 'Address')
    where UDN = @p1
  
  exec nxShortcutSynchronize @p1
end
go

Вызов nxShortcutSynchronize нужен всякий раз, когда вы изменяете поля Name, No или Date на уровне абстрактного документа, то есть в таблице Docs. Она синхронизирует новые значения полей с таковыми же полями ссылок на этот документ, если они существуют в системе.

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

create procedure Contact_read_view_
  @p1 int, @p2 int, @p3 int
as begin
  exec Contact_read_edit_ @p1, @p2, @p3
  exec nxFormSetTitle @p1, 'Просмотр контакта'
  exec nxFormSetReadonly @p1
end
go

Вызов nxFormSetReadonly сделает все поля, объявленные в Contact_edit_read_, доступными "только для чтения".

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

create procedure Contact_filt_read_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFilterChangeFormat @p1, 'Docs.Name', 'sПолное название'
  exec nxFilterChangeFormat @p1, 'Docs.No', 'sАдрес'
  exec nxFilterAddString @p1, 'Contact.UDN', 'sТелефон', 'Contact.Phone'
end
go

В нашем обработчике с помощью процедуры nxFilterChangeFormat мы меняем формат уже объявленных предком (класс Doc) элементов, связанных с полями Name и No, в соответствии с использованием этих полей в нашем классе Contact К фильтру также добавляется собственный элемент класса Contact строкового типа для поля Phone.

Немного о параметрах процедуры nxFilterAddString. Первым, как обычно, идет указатель на документ @p1. Далее, строка вида 'Таблица.Поле' для полей класса Doc или 'Таблица.ПолеСвязи' для подклассов. Затем следуют формат поля и строка для записи вида 'Таблица.Поле' или '(подзапрос)'.

В конце файла следует добавить вызов

exec DocPatch
go

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

Нажимаем "Execute query" (Ctrl+E) в QA и, если вы не допустили синтаксических ошибок, то в окне сообщений появится строка "The command(s) completed successfully". Итак, наш класс Contact уже в системе, но в силу абстрактности вы не можете создавать его экземпляры. Поэтому, аналогичным образом создадим несколько обработчиков для класса Company. При этом наша реализация для Contact будет унаследована, то есть, например, в обработчиках событий read или filt необходимо будет только добавить описания новых полей. Чтобы не повторяться снова, я только приведу и прокомментирую отличия кода процедур-обработчиков, который мы напишем в файле Company.sql.

create procedure Company_getp_list_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxRenameProperty 'view', 'Просмотр контакта-фирмы'
  exec nxRenameProperty 'edit', 'Редактирование контакта-фирмы'
end
go

exec InsPrivilege 'view_Company', 'Просмотр контакта-фирмы', 'Контакты'
exec InsPrivilege 'edit_Company', 'Редактирование контакта-фирмы', 'Контакты'
exec CompileSecurity
go

Поскольку операции просмотра и редактирования уже объявлены в Contact, то мы лишь переименуем их названия, хотя это совсем не обязательно (все методы виртуальные). Далее необходимо объявить привилегии на выполнение операций при помощи InsPrivilege и перекомпилировать матрицу доступа при помощи CompileSecurity. Эти процедуры также можно выполнять многократно (InsPrivilege только обновляет уже существующую привилегию), поэтому для удобства мы расположим их сразу после объявления операций.

В обработчике gets мы изменим формат поля FullName и добавим Fax.

create procedure Company_gets_edit_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFormSetTitle @p1, 'Новый контакт-фирма'
  exec nxFormValueSetFormat @p1, 'FullName', 'sНазвание фирмы'
  exec nxFormEditString @p1, 'Fax', 'sФакс', ''
end
go

Соответственно, при создании нам нужно будет вставить запись в Company.

create procedure Company_cre_edit_
  @p1 int, @p2 int, @p3 int
as begin
  insert into Company (UDN, Fax)
    select @p2, dbo.nxFormGetString(@p1, 'Fax')
end
go

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

create procedure Company_read_edit_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFormSetTitle @p1, 'Редактирование контакта-фирмы'
  declare @S string
  exec nxFormValueSetFormat @p1, 'FullName', 'sНазвание фирмы'
  set @S = (select Fax from Company where UDN = @p1)
  exec nxFormEditString @p1, 'Fax', 'sФакс', @S
end
go

и

create procedure Company_put_edit_
  @p1 int, @p2 int, @p3 int
as begin
  update Company
    set Fax = dbo.nxFormGetString(@p1, 'Fax')
    where UDN = @p1
end
go

В обработчике filt мы также изменим формат поля FullName и добавим Fax.

create procedure Company_filt_read_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFilterChangeFormat @p1, 'Docs.Name', 'sНазвание фирмы'
  exec nxFilterAddString @p1, 'Company.UDN', 'sФакс', 'Company.Fax'
end
go

Теперь, если выполнить скрипт, то в проводнике вы сможете создавать, просматривать, редактировать и удалять документы класса "Компания".

Рис.4. Создание нового документа - кампании

Рис.5. Созданный документ в списке проводника и его контекстное меню.

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

Нам осталось аналогичным образом создать обработчики событий для третьего класса Person, в файле Person.sql. Важное отличие этого класса состоит в наличии поля - объектной ссылки на документ класса Company. Чтобы реализовать отображения этого поля, нам понадобится несколько отличный формат, чем тот, который мы использовали для строковых полей. Вот так будут выглядеть обработчики для событий gets и read:

create procedure Person_gets_edit_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFormSetTitle @p1, 'Новое контактное лицо'
  exec nxFormValueSetFormat @p1, 'FullName', 'sФИО'
  exec nxFormEditString @p1, 'Mobile', 'sМобильный'
  exec nxFormEditInt @p1, 'Company', 'dФирма^home=ContactCompanies^null'
end
go

create procedure Person_read_edit_
  @p1 int, @p2 int, @p3 int
as begin
  exec nxFormSetTitle @p1, 'Редактирование контактного лица'
  exec nxFormValueSetFormat @p1, 'FullName', 'sФИО'
  declare @S string, @I int
  set @S = (select Mobile from Person where UDN = @p1)
  exec nxFormEditString @p1, 'Mobile', 'sМобильный', @S
  select @I = (select CompanyUDN from Person where UDN = @p1)
  exec nxFormEditInt @p1, 'Company', 'dФирма^home=ContactCompanies^null', @I
end
go

Для объявления поля - объектной ссылки используется тип Int и соответствующая процедура nxFormEditInt , но со специальным форматом 'dНазвание поля'. Клауза '^home=имя корня' определяет папку, в которую попадает пользователь для выбора документа в поле (мы её уже определили заранее), кликнув на пиктограмму в поле справа, '^null' говорит о том, что поле не обязательно к заполнению.

Рис.6. Создание нового контактного лица.

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

create procedure Company_off_Person
  @p1 int, @p2 int, @p3 int
as begin
  if exists(select 1 from Person where CompanyUDN = @p1) begin
    raiserror('У фирмы есть контактные лица', 1, 1)
    return dbo.nxRETURN_ERROR()
  end
end
go

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

Рис 7. Сообщение об ошибке.

Создание инсталляционного скрипта

Теперь, когда наш пример завершен, создадим файл SQL-скрипта, который устанавливает модуль на другой базе данных NEXUS.

Для этого откроем командный файл build.cmd и откорректируем его содержимое следующим образом:

@echo off

set INSTALL_FILE=../Bin/Contacts.sql

cd Sources
type _PreInstall.sql > %INSTALL_FILE%
echo. >> %INSTALL_FILE%
type Contact.sql >> %INSTALL_FILE%
echo. >> %INSTALL_FILE%
type Company.sql >> %INSTALL_FILE%
echo. >> %INSTALL_FILE%
type Person.sql >> %INSTALL_FILE%
echo. >> %INSTALL_FILE%
type _PostInstall.sql >> %INSTALL_FILE%
cd ..

Теперь после запуска build.cmd в каталоге Bin будет создан файл, выполнив который на другой БД NEXUS, вы установите ваш модуль в системе.

Краткий итог

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

Полный текст примера вы можете скачать на этой странице.


 
Много файлов (2). [Показать файлы/форму]
Комментариев нет. [Показать комментарии/форму]

Рейтинг@Mail.ru Яндекс цитирования Арбинада - софтотворение и софтостроение