> For the complete documentation index, see [llms.txt](https://wfsys.gitbook.io/wt-practice/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wfsys.gitbook.io/wt-practice/main/lesson_main_form.md).

# Урок 6. Главная форма

На прошлом уроке мы рассмотрели разные варианты удаления связанных записей и остановились на варианте с архивированием.

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

В этом уроке мы:

* познакомимся с главным меню [MainMenu](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/menus/main_menu);
* рассмотрим  [SecondaryGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/secondary_dc).

  <div data-gb-custom-block data-tag="hint" data-style="success" class="hint hint-success"><p>Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье <a href="https://wfsys.gitbook.io/workflow-technology/setting-up-dev-environment/manual-deployment-project">Разворачивание проекта</a>.</p><p>При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела <a href="/pages/-MdvNprVvngfwvsUA7NA#answer">Ответы</a> прошлого урока. Скопируйте папки <em>Forms</em>, <em>Workflow</em> и <em>Patterns</em> в папку с развернутым проектом, например, в папку <em>D:\WT\Projects\Template\Projects\1. Template</em>.</p><p>Инструкция по подключению шаблонов находится по <a href="/pages/-M_eX46yXXalFkHRNC7X#podklyuchenie-shablonov-k-proektu">ссылке</a>.</p></div>

## Список заказов и карточка заказа <a href="#list-of-orders-and-order-card" id="list-of-orders-and-order-card"></a>

По итогам текущего урока у нас должны получиться формы вида:

<figure><img src="/files/EVnWy4skbtcl1EqFKtVw" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/NBGNNeYdGGWpJXUaLShR" alt=""><figcaption></figcaption></figure>

### Таблица в базе данных

Начнем с таблицы в базе данных.

Создайте таблицу **template.order** для заказов. В таблице будем хранить идентификатор клиента (client\_id), номер заказа (order\_number), дату и время заказа (order\_date) и комментарий к заказу (description). Все поля, кроме комментария, должны иметь ограничение **not null**.

Для дат со временем используем тип **timestamp without time zone**, который не хранит информацию о временной зоне. В таком случае PostgreSQL предполагает, что все временные данные принадлежат одной временной зоне, и не производит никаких преобразований. О том, как происходит работа с временными зонами в платформе Workflow Technology, мы поговорим далее в уроке.

Для удаления заказов в таблице будем использовать колонку **deleted** типа boolean с ограничением **not null** и значение по умолчанию **false**. Значение в этой колонке будем обновлять через update-запрос при попытке удалить заказ. Таким образом, мы будем хранить в базе данных информацию обо всех когда-либо созданных заказах. А в одном из будущих уроков реализуем форму для просмотра удаленных заказов.

### Главная форма <a href="#main-form" id="main-form"></a>

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

Давайте в последний раз заменим стартовую форму. Для этого файл TemplateStart.xml переименуем в TemplateClientList.xml, создадим форму для списка заказов и назовем ее TemplateStart.xml.

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

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

<figure><img src="/files/m9Euql3zicT1o4SquYOH" alt=""><figcaption></figcaption></figure>

Таблица на форме списка заказов должна содержать колонки OrderId, ClientId, CityId, № (RowNumber), Номер заказа (OrderNumber), Дата и время заказа (OrderDate), Город (CityTitle) и Клиент (ClientTitle).&#x20;

В запрос для получения списка заказов и PrimaryGetDataConnection для таблицы не добавляйте поля **ClientTitle** и **CityTitle**. Позже в уроке мы рассмотрим, как динамически подтягивать имена клиентов и названия городов в таблицу, имея только идентификаторы в колонках **ClientId** и **CityId**.&#x20;

Для колонки **OrderDate** используйте вложенный необязательный тэг [`<DataType>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column#column_data_type), задающий формат строки, в которую будет преобразовано значение из источника данных при заполнении таблицы:

```xml
<Column Name="OrderDate" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Дата и время заказа</Title>
  <Width>130</Width>
  <AutoSizeMode Value="None" />
  <Alignment Value="MiddleCenter" />
  <DataType Type="DateTimeDataType" Format="dd.MM.yyyy HH:mm" />
</Column>
```

Для удаления заказов реализуйте update-запрос на обновление значения в колонках **deleted** и **date\_deleted**. Не забудьте в select-запросе на получение списка заказов исключить удаленные заказы, чтобы не отображать их на главной форме.

Реализуйте выделение нужной строки в таблице заказов после создания и редактирования заказа.

{% hint style="info" %}
Команда `INSERT` позволяет получать данные из добавленной записи без использования дополнительного запроса к базе данных. Для этого используется предложение `RETURNING`.

С его помощью мы можем получить идентификатор добавленного заказа и вернуть его на форму. Результат запроса будет записан в результат выполнения команды SaveCommand, откуда его можно сохранить в параметр формы.
{% endhint %}

На кнопках используйте режим **DisabledMode.**

### Карточка заказа <a href="#order-card" id="order-card"></a>

Создайте форму карточки заказа (TemplateOrderEdit.xml) с полями: Номер заказа (OrderNumberTextBox), Дата и время заказа (OrderDateDateTimePicker), Клиент (ClientComboBox), Город (CityTextBox), Телефон клиента (PhoneTextBox) и Комментарий к заказу (DescriptionTextBox).

Для выпадающего списка клиентов **ClientComboBox** создайте отдельный запрос на получения списка клиентов и назовите его, например, ClientShortSelectSqlQuery. Этот запрос должен возвращать поля ClientId, ClientTitle, CityTitle и Phone. Для запроса ClientShortSelectSqlQuery создайте отдельный SqlQueryPermission (например, ClientOrderViewSqlQueryPermission), который добавьте в роли **OrderViewRole** и **OrderEditRole**.&#x20;

Поля "Город" (CityTextBox) и "Телефон клиента" (PhoneTextBox) оставьте без указания источника данных в тэге `<Text>` и сделайте их нередактируемыми, указав признак `<ReadOnly>` со значением **True**. Эти поля будут информативными и будут заполнятся данными из **ClientShortSelectSqlQuery** для клиента, выбранного в выпадающем списке **ClientComboBox**. Позже в уроке рассмотрим инструмент, который позволяет это сделать.

Так как пользователь не может редактировать значение объектов CityTextBox и PhoneTextBox, то исключим их из проверки свойства формы [FormChanged](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/form#get_form_changed), прописав в тэге `<MyObject>` атрибут `ChangeForm` со значением **False**.

Для того чтобы перевести текстовое поле "Комментарий к заказу" (DescriptionTextBox) в многострочный режим, добавьте в описание поля тэг [`<Multiline>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/textbox#multiline) со значением True и настройте высоту поля.

Не забудьте на форме реализовать [паттерн **onClose**](https://wfsys.gitbook.io/wt-practice/main/lesson_4#pattern-onclose)**.**

По завершении у вас будет форма вида:

<figure><img src="/files/jEJAKsOrIavBVt563e7m" alt=""><figcaption></figcaption></figure>

## Временные зоны <a href="#time-zone" id="time-zone"></a>

Так как в карточке заказа мы используем [DateTimePicker](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/date_time_picker) для даты со временем, необходимо сделать отступление и поговорить о том, как происходит работа со временем и временными зонами в платформе. Подробно по этому вопросу можно почитать по [ссылке](https://wfsys.gitbook.io/wt-knowledge-base/platforma-wt/time_zone), а сейчас затронем только те моменты, которые важны для текущей работы.

#### Правила передачи времени между сервером и клиентом <a href="#rules_timezone" id="rules_timezone"></a>

Дата со временем (в формате даты) между клиентом и сервером всегда передается в UTC. Когда дату со временем передаем с формы на сервер, платформа переводит время из временной зоны клиента в UTC, а сервер полученную дату переводит из UTC в свою временную зону.

Если в DateTimePicker используется только дата (без времени) или только время, то на сервер значение DateTimePicker отправится без перевода в UTC. Пример такой отправки мы наблюдаем в карточке клиента с полем "Дата рождения". А если в DateTimePicker используется дата и время, то они будут приведены к UTC. Эта логика будет отрабатываться в карточке заказа.

Аналогичное преобразование происходит и в обратную сторону. Сервер получает дату со временем из база данных, переводит ее в UTC и передает клиенту. Клиент, получив дату со временем, переводит ее из UTC в свою временную зону.

#### Временная зона сервера <a href="#server_timezone" id="server_timezone"></a>

Даты со временем хранятся в базе данных во временной зоне сервера, по умолчанию это **Etc/GMT**. Чтобы задать свою временную зону, необходимо в серверный файл настроек (appsettings.json) внести строки:

{% code title="appsettings.json" %}

```json
"TimeZone": {
  "TimeZone": "Europe/Moscow",
}
```

{% endcode %}

В качестве значения вложенного поля TimeZone ожидается имя временной зоны в формате IANA. В базе данных в таблице **public.time\_zone\_info** в поле **name** указаны все доступные имена временных зон, поддерживаемые платформой.

#### Временная зона клиента <a href="#client_timezone" id="client_timezone"></a>

Для хранения временной зоны клиента используется колонка **time\_zone\_info\_id** в таблице **public."user"**. По умолчанию там стоит идентификатор временной зоны **Europe/Moscow**. На временную зону клиента не влияют настройки часового пояса в системе Windows.

## Карточка заказа <a href="#order-card-detail" id="order-card-detail"></a>

Давайте вернемся в файл карточки заказа (TemplateOrderEdit.xml) и доделаем поля "Город" (CityTextBox) и "Телефон клиента" (PhoneTextBox),&#x20;

Для получения данных о выбранном клиенте будем использовать вторично загружающее соединение с данными [SecondaryGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/secondary_dc), для которого в качестве источника данных укажем **ClientPrimaryGetDataConnection** и добавим фильтр по полю **ClientId**. В качестве значения фильтра будем брать значение из выпадающего списка **ClientComboBox**.

{% code title="TemplateOrderEdit.xml" %}

```xml
<DataConnection Name="ClientSecondaryGetDataConnection" Type="SecondaryGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="ClientPrimaryGetDataConnection" />
  <Filter>
    <Field NativeName="ClientId" />
    <Value>
      <Object Name="ClientComboBox" />
    </Value>
    <DataType Type="IntegerDataType" />
  </Filter>
</DataConnection>
```

{% endcode %}

SecondaryGetDataConnection можно использовать на форме так же, как и PrimaryGetDataConnection, обращаясь к его полям, которые наследуются из источника. Например, для задания значения тэга `<Text>` у объекта **CityTextBox** будем использовать запись вида:

```xml
<DataConnection SourceDataConnection="ClientSecondaryGetDataConnection">
  <Fields>
    <Field Name="CityTitle"/>
  </Fields>
</DataConnection>
```

Аналогично заполните тэг `<Text>` у объекта **PhoneTextBox**.

Запустите проект и проверьте отображение информации о выбранном клиенте в карточке заказа.

<figure><img src="/files/tmMLs6vz0xDrljearW0H" alt=""><figcaption></figcaption></figure>

Отлично! Теперь можем вернуться в файл главной формы (TemplateStart.xml). Оставшуюся часть урока полностью посвятим ей.

## Главная форма <a href="#main-form-detail" id="main-form-detail"></a>

### Подстановка значений в колонки таблицы <a href="#substitution-values-in-table-columns" id="substitution-values-in-table-columns"></a>

В таблице заказов остались незаполненными колонки "Город" (CityTitle) и "Клиент" (ClientTitle). Давайте рассмотрим механизм, который позволяет подтягивать в колонку таблицы значение из стороннего источника данных на основе значения из другой колонки таблицы.

Создайте **ClientSimpleSelectSqlQuery** с двумя полями ClientId и ClientTitle, и поместите его в разрешение **ClientOrderViewSqlQueryPermission**, так как он связан с просмотром заказов.

В файле главной формы создайте **ClientPrimaryGetDataConnection** на этот запрос. Этот DataConnection будем использовать в качестве источника данных для заполнения колонки **ClientTitle** таблицы **OrderDatabaseTable**.&#x20;

Для подстановки значения в колонку используется вложенный тэг [`<Substitution>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column#column_substitution) у тэга `<Column>`.  Добавим в описание колонки ClientTitle таблицы OrderDatabaseTable тэг `<Substitution>`:

```xml
<Column Name="ClientTitle" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Клиент</Title>
  <Width>300</Width>
  <AutoSizeMode Value="Fill" />
  <Substitution SourceColumn="ClientId">
    <DataConnection SourceDataConnection="ClientPrimaryGetDataConnection">
      <Fields>
        <Field Name="ClientId" />
        <Field Name="ClientTitle" />
      </Fields>
    </DataConnection>
  </Substitution>
</Column>
```

Атрибут `SourceColumn` тэга `<Substitution>` ожидает имя колонки, на основе значения которой будут подставляться данные для отображения. В качестве значения тэга `<Substitution>` ожидается матрица из двух колонок: первая должна содержать ключ, по которому будет происходить выборка значения, а вторая - самое значение.

Аналогично сделайте для колонки **CityTitle**. Создайте для этого новый запрос **CitySimpleSelectSqlQuery**, который будет возвращать список всех городов (архивных и неархивных), и создайте для него отдельное разрешение **CityOrderViewSqlQueryPermission**.

Запустите проект и проверьте корректность заполнения таблицы.

<figure><img src="/files/Pfrf7YwGnpGOAa004O6l" alt=""><figcaption></figcaption></figure>

### Главное меню <a href="#main-menu" id="main-menu"></a>

Сейчас у нас нет возможности просматривать списки клиентов и городов. Давайте добавим главное меню [`<MainMenu>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/menus/main_menu), чтобы иметь доступ к нашим спискам. Но для начала самостоятельно создайте команды **ClientListFormShowCommand** и **CityListFormShowCommand** для открытия форм списка клиентов и списка городов соответственно. А затем скопируйте представленный ниже код и добавьте его в файл стартовой формы перед описанием `<MyObjects>`.

{% code title="TemplateStart.xml" %}

```xml
<MainMenu>
  <MenuItem Name="FileMenuItem" Type="MenuItem">
    <Title>Файл</Title>
    <Items>
      <MenuItem Name="FileRefreshMenuItem" Type="MenuItem">
        <Title>Обновить</Title>
        <Command Name="" />
      </MenuItem>

      <MenuItem Name="FileSeparator" Type="Separator" />

      <MenuItem Name="FileExitMenuItem" Type="MenuItem">
        <Title>Выход</Title>
        <Command Name="FormCloseCommand" />
      </MenuItem>
    </Items>
  </MenuItem>

  <MenuItem Name="ListMenuItem" Type="MenuItem">
    <Title>Списки</Title>
    <Items>
      <MenuItem Name="CityMenuItem" Type="MenuItem">
        <Title>Города...</Title>
        <Command Name="CityListFormShowCommand" />
      </MenuItem>

      <MenuItem Name="ClientMenuItem" Type="MenuItem">
        <Title>Клиенты...</Title>
        <Command Name="ClientListFormShowCommand" />
      </MenuItem>
    </Items>
  </MenuItem>
</MainMenu>
```

{% endcode %}

В пункте меню **FileRefreshMenuItem** (Обновить) в качестве значения атрибута `Name` тэга `<Command>` укажите имя команды на обновление PrimaryGetDataConnection, который используете в таблице заказов.

Если сейчас запустим проект, то на стартовой форме не увидим главное меню. Это связано с тем, что меню размещается в верхней части формы, там, где расположена панель **HeadPanel**, а точнее, под этой панелью. Давайте немного сдвинем панель вниз, задав координате `<Top>` значение **24**:

```xml
<MyObject Name="HeadPanel" Type="Panel" Assembly="BaseControls">
  <Top>24</Top>
  <Left>0</Left>
  <Height>50</Height>
  <Width>
    <Form>
      <Property Name="Width" />
    </Form>
  </Width>
  <BackColor>HeadBackgroundColor</BackColor>
  
  <!-- ... -->
  
</MyObject>
```

Запустим проект и проверим отображение главного меню.

<figure><img src="/files/4MWlrkrkGmb91OVR1V42" alt=""><figcaption></figcaption></figure>

Отлично! Теперь мы имеем доступ к нашим спискам и можем их редактировать.

В карточке клиента у нас есть кнопка **CityListButton** для доступа к списку городов. Ее можно удалить - она нам больше не пригодится. Удалите ее и все элементы, связанные с логикой обработки результата с формы списка городов.

## Самостоятельно <a href="#self-work" id="self-work"></a>

#### Обновление списков городов и клиентов на главной форме

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

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

Затем на главной форме для **CityPrimaryGetDataConnection** и **ClientPrimaryGetDataConnection** создайте отдельные команды [DataConnectionRefreshCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/dc_refresh_command). Для каждой команды создайте отдельный [Execution](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/executions), который будет проверять параметр Updated у соответствующей команды [FormShowCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/form_show_command).

#### Архивирование записей клиентов

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

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

## Итоги <a href="#results" id="results"></a>

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

Также на уроке мы познакомились с объектом [MainMenu](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/menus/main_menu), который применяется для организации классического доступа к различным функциям приложения.

Рассмотрели механизм подстановки данных в колонку таблицы [DatabaseTable](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable), который в коде реализуется через вложенный тэг [`<Substitution>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column#column_substitution) у тэга [`<Column>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column).

## Ответы <a href="#answer" id="answer"></a>

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

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-type="content-ref"></th></tr></thead><tbody><tr><td>lesson6-answer.zip</td><td><a href="https://wfsys.ru/download/wt_practice_desktop_answers/lesson6-answer.zip">https://wfsys.ru/download/wt_practice_desktop_answers/lesson6-answer.zip</a></td></tr></tbody></table>
