# Урок 10. Паттерн Add/Edit

В этом уроке мы рассмотрим подход к работе со сложными сущностями с иерархической структурой.&#x20;

{% hint style="success" %}
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье [Разворачивание проекта](https://wfsys.gitbook.io/workflow-technology/setting-up-dev-environment/manual-deployment-project).

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела [Ответы](https://wfsys.gitbook.io/wt-practice/lesson_categories_list#answer) прошлого урока. Скопируйте папки *Forms*, *Workflow* и *Patterns* в папку с развернутым проектом, например, в папку *D:\WT\Projects\Template\Projects\1. Template*.

Инструкция по подключению шаблонов находится по [ссылке](https://wfsys.gitbook.io/wt-practice/lesson_list_form#podklyuchenie-shablonov-k-proektu).
{% endhint %}

## Подготовка карточки заказа

### Форма заказа

Изменим карточку заказа. Добавим в нее таблицу с позициями заказа и итоговой суммой к оплате.

В базе данных создадим таблицу:

```sql
CREATE SEQUENCE template.order_position_id_seq;
CREATE TABLE template.order_position
(
  order_position_id bigint NOT NULL DEFAULT nextval('template.order_position_id_seq'::regclass),
  order_id bigint NOT NULL,
  material_id bigint NOT NULL,
  quantity numeric NOT NULL,
  unit_price numeric NOT NULL,
  CONSTRAINT pk_order_position_id PRIMARY KEY (order_position_id),
  CONSTRAINT fk_material_id FOREIGN KEY (material_id)
      REFERENCES template.material (material_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_order_id FOREIGN KEY (order_id)
      REFERENCES template."order" (order_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);
```

Перейдите в файл TemplateOrderEdit.xml и создайте в ContentPanel две панели:

* **CommonPanel** - панель для отображения общих данных, таких как информация о клиенте, номер и дата заказа, комментарий к заказу и сумма к оплате;
* **OrderPositionPanel** - панель для таблицы с позициями в заказе.&#x20;

В панели CommonPanel создайте три панели, которые отделите друг от друга разделителями:

* **ClientPanel** - сюда перенесите поля связанные с клиентом;
* **OrderPanel** - сюда перенесите поля номер и дату заказа и комментарий к заказу;
* **TotalPanel** - Здесь создайте текстовое поле "Всего к оплате" (TotalSumToPayTextBox).

В панели OrderPositionPanel создайте таблицу с помощью паттерна Table.

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FzhrzOcD3TLqmdOqmWPDE%2F01.png?alt=media&#x26;token=e15dba43-c16e-4e29-b08f-a43f4ebf409a" alt=""><figcaption></figcaption></figure>

Добавьте атрибут `ChangeForm="False"` для таблицы позиций заказа, чтобы изменение ее источника данных не приводило к изменению формы и активации кнопки "Сохранить" - сохранение позиции заказа уже было на дочерней форме.

Добавим в таблицу **OrderPositionDatabaseTable** колонки:

```xml
<Column Name="MaterialId" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Visible>False</Visible>
</Column>
<Column Name="Quantity" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Кол-во</Title>
  <Width>60</Width>
  <AutoSizeMode Value="None" />
  <DataType Type="DecimalDataType" />
  <Alignment Value="MiddleRight" />
</Column>
<Column Name="UnitPrice" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Цена за единицу</Title>
  <Width>80</Width>
  <AutoSizeMode Value="None" />
  <DataType Type="DecimalDataType" Format="N2" />
  <Alignment Value="MiddleRight" />
</Column>
<Column Name="TotalPrice" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Стоимость</Title>
  <Width>100</Width>
  <AutoSizeMode Value="None" />
  <DataType Type="DecimalDataType" Format="N2" />
  <Alignment Value="MiddleRight" />
  <Calculate>
    <Expression>Quantity * UnitPrice</Expression>
  </Calculate>
</Column>
```

А чтобы текст в заголовке колонки **UnitPrice** полностью отображался, добавим в описание таблицы тэг [`<ColumnHeadersHeight>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable#column_headers_height) со значением атрибута `Value` равным **50**.

Создадим переменную [Variable](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/variable), в которой будем считать сумму значений во всех строках колонки **TotalPrice** таблицы OrderPositionDatabaseTable:

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

```xml
<MyObject Name="TotalSumToPayVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Object Name="OrderPositionDatabaseTable">
      <Property Name="ColumnSum">
        <Parameters>
          <Parameter Name="ColumnName">TotalPrice</Parameter>
        </Parameters>
      </Property>
    </Object>
  </Value>
</MyObject>
```

{% endcode %}

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

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

```xml
<MyObject Name="TotalSumToPayTextBox" Type="TextBox" Assembly="BaseControls" ChangeForm="False">
  <Top>
    <Object Name="TotalSumToPayLabel">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Object Name="TotalSumToPayLabel">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="TotalSumToPayLabel">
      <Property Name="Width" />
    </Object>
  </Width>
  <TabIndex>1</TabIndex>
  <TextAlign>Right</TextAlign>
  <ReadOnly>True</ReadOnly>
  <Text>
    <DataTypeFormat Type="DecimalDataType" Format="N2">
      <Object Name="TotalSumToPayVariable" />
    </DataTypeFormat>
  </Text>
</MyObject>
```

{% endcode %}

Здесь используем универсальное значение [DataTypeFormat](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/values/data_type_format) для задания формата строки, в которую будет преобразовано значение переменной.

Запустите приложение и проверьте загрузку формы заказа и отображение элементов на ней:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fv3jzQ6yQC1tKRj5EHccM%2Fimage.png?alt=media&#x26;token=adc5aa7d-7ab8-4169-b686-9c342bd7a405" alt=""><figcaption></figcaption></figure>

Перейдем в серверный xml-файл. Переименуем запрос OrderPositionSelectSqlQuery в **OrderPositionByOrderIdSelectSqlQuery** и перепишем его текст:

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

```xml
<SqlQuery Name="OrderPositionByOrderIdSelectSqlQuery">
  <Text>
    SELECT
      OP.order_position_id AS "OrderPositionId",
      OP.material_id AS "MaterialId",
      OP.quantity AS "Quantity",
      OP.unit_price AS "UnitPrice"
    FROM
      template.order_position OP
    WHERE
      OP.order_id = {OrderId}
    ORDER BY OP.order_position_id;
  </Text>
</SqlQuery>
```

{% endcode %}

Из запроса мы убрали поле Title, так как одноименную колонку в таблице **OrderPositionDatabaseTable** будем заполнять с помощью тэга [`<Substitution>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column#column_substitution) по колонке MaterialId. Для этого подготовим запрос для получения списка ТМЦ:

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

```xml
<SqlQuery Name="MaterialSimpleSelectSqlQuery">
  <Text>
    SELECT
      M.material_id AS "MaterialId",
      M.title AS "Title",
      MC.title AS "MaterialCategoryTitle",
      U.short_title AS "UnitShortTitle"
    FROM
      template.material M
      LEFT JOIN template.material_category MC USING(material_category_id)
      LEFT JOIN template.unit U USING(unit_id)
    ORDER BY M.title;
  </Text>
</SqlQuery>
```

{% endcode %}

В запрос добавили поля MaterialCategoryTitle и UnitShortTitle - они нам позже понадобятся.

В следующем разделе создадим Permission, в который добавим запрос MaterialSimpleSelectSqlQuery.

В запросе OrderPositionDeleteSqlQuery замените вызов функции template.order\_position\_try\_delete() на DELETE-запрос.

Вернемся в файл TemplateOrderEdit.xml. Скорректируем соединение с данными **OrderPositionPrimaryGetDataConnection**, переделав его на переименованный запрос и добавив новые поля:

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

```xml
<DataConnection Name="OrderPositionPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="OrderPositionByOrderIdSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="OrderPositionId" />
      <Field Name="MaterialId" />
      <Field Name="Quantity" />
      <Field Name="UnitPrice" />
    </Fields>
    <Parameters>
      <Parameter NativeName="OrderId">
        <Value>
          <Parameter Name="OrderId" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

А так же добавим новое соединение с данными, которое будем использовать в тэге `<Substitution>` колонки **Title** таблицы **OrderPositionDatabaseTable**:

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

```xml
<DataConnection Name="MaterialPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="MaterialSimpleSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="MaterialId" />
      <Field Name="Title" />
      <Field Name="MaterialCategoryTitle" />
      <Field Name="UnitShortTitle" />
    </Fields>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

Заменим описание колонки Title на код:

```xml
<Column Name="Title" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Наименование</Title>
  <Width>300</Width>
  <MinimumWidth>100</MinimumWidth>
  <AutoSizeMode Value="Fill" />
  <Substitution SourceColumn="MaterialId">
    <DataConnection SourceDataConnection="MaterialPrimaryGetDataConnection">
      <Fields>
        <Field Name="MaterialId" />
        <Field Name="Title" />
      </Fields>
    </DataConnection>
  </Substitution>
</Column>
```

Команды на открытие формы TemplateOrderPositionEdit.xml сделаем [**модальными**](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/form_show_command#show).

А в команду **OrderPositionAddFormShowCommand** добавим параметр **OrderId**, т.к. позиции заказа должны быть привязаны к конкретному идентификатору заказа. Сразу создайте параметр форм OrderId в карточке заказа.

### Карточка позиции заказа

Перейдем в серверный xml-файл и скорректируем запрос OrderPositionByIdSelectSqlQuery:

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

```xml
<SqlQuery Name="OrderPositionByIdSelectSqlQuery">
  <Text>
    SELECT
      OP.material_id AS "MaterialId",
      OP.quantity AS "Quantity",
      OP.unit_price AS "UnitPrice"
    FROM
      template.order_position OP
    WHERE
      OP.order_position_id = {OrderPositionId};
  </Text>
</SqlQuery>
```

{% endcode %}

Перейдем в файл TemplateOrderPositionEdit.xml и скорректируем первичное соединение с данными:

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

```xml
<DataConnection Name="OrderPositionPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <ManualLoad>
    <Not>
      <Parameter Name="Edit" />
    </Not>
  </ManualLoad>
  <SqlQuery Name="OrderPositionByIdSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="MaterialId" />
      <Field Name="Quantity" />
      <Field Name="UnitPrice" />
    </Fields>
    <Parameters>
      <Parameter NativeName="OrderPositionId">
        <Value>
          <Parameter Name="OrderPositionId" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

Удалите объекты TitleLabel и TitleTextBox и весь код, ссылающийся на них.&#x20;

#### Петтерн редактируемого выпадающего списка <a href="#pettern-of-editable-combobox" id="pettern-of-editable-combobox"></a>

Скачайте новый архив с паттернами:

{% file src="<https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fc46LLng7HaDhVDx0qv9Q%2Flesson10-Patterns.zip?alt=media&token=4ecb2373-11ce-4a8e-936e-30c9ef60b96c>" %}

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

![](https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fzz2DMj7d8GZSpvManvLc%2Fimage.png?alt=media\&token=d81cbb3f-29c8-4b3d-80c8-ed18db0a2c4c)

Выполним следующие настройки паттерна:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FEseAKrnAQvtTGxS8MUZN%2Fimage.png?alt=media&#x26;token=a24c14f9-bd88-4597-ae53-6dd376ba06fa" alt=""><figcaption></figcaption></figure>

Так как это первое поле на форме, то последние два поля оставим пустыми.

После применения паттерна перейдем в серверный xml-файл и внесем некоторые изменения.&#x20;

Созданный **OrderPositionMaterialViewSqlQueryPermission** добавим в роли **OrderViewRole** и **OrderEditRole**. Добавим в этот Permission запрос MaterialSimpleSelectSqlQuery.

Паттерн создал команды на открытие формы для добавления/редактирования ТМЦ и формы списка ТМЦ. На обе формы передается параметр MaterialTitle. Добавьте этот параметр в описание форм и подставляйте его значение в соответствующее поле в карточке ТМЦ.

Скорректируем запрос **MaterialShortSelectSqlQuery**, который был создан при выполнении паттерна, добавив необходимые поля:

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

```xml
<SqlQuery Name="MaterialShortSelectSqlQuery">
  <Text>
    SELECT
      M.material_id AS "MaterialId",
      M.title AS "Title",
      M.material_category_id AS "MaterialCategoryId",
      MC.title AS "MaterialCategoryTitle",
      U.short_title AS "UnitShortTitle",
      M.unit_price AS "UnitPrice"
    FROM
      template.material M
      LEFT JOIN template.material_category MC USING(material_category_id)
      LEFT JOIN template.unit U USING(unit_id)
    WHERE
      NOT M.archive OR M.material_id = {MaterialId}
    ORDER BY M.title;
  </Text>
</SqlQuery>
```

{% endcode %}

Вернемся в файл TemplateOrderPositionEdit.xml и поправим соединение с данными для выпадающего списка:

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

```xml
<DataConnection Name="MaterialShortPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="MaterialShortSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="MaterialId" />
      <Field Name="Title" />
      <Field Name="MaterialCategoryId" />
      <Field Name="MaterialCategoryTitle" />
      <Field Name="UnitShortTitle" />
      <Field Name="UnitPrice" />
    </Fields>
    <Parameters>
      <Parameter NativeName="MaterialId">
        <Value>
          <DataConnection SourceDataConnection="OrderPositionPrimaryGetDataConnection">
            <Fields>
              <Field Name="MaterialId" />
            </Fields>
          </DataConnection>
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

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

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

```xml
<DataConnection Name="MaterialSecondaryGetDataConnection" Type="SecondaryGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="MaterialShortPrimaryGetDataConnection" />
  <Filter>
    <Field NativeName="MaterialId" />
    <Value>
      <Object Name="MaterialComboBox" />
    </Value>
    <DataType Type="IntegerDataType" />
  </Filter>
</DataConnection>
```

{% endcode %}

Скорректируем значение у тэгов `<Top>` и `<Left>` нового объекта **MaterialLabel**, присвоив им константы 5 и 10 соответственно.Таким образом, синтаксис объекта будет иметь вид:

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

```xml
<MyObject Name="MaterialComboBox" Type="ComboBox" Assembly="BaseControls">
  <Top>
    <Object Name="MaterialLabel">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Object Name="MaterialLabel">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Calculate>
      <Expression>{0} - {1}*2 - {2} - {3} - 10</Expression>
      <Items>
        <Item>
          <Object Name="ContentPanel">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="MaterialComboBox">
            <Property Name="Left" />
          </Object>
        </Item>
        <Item>
          <Object Name="MaterialAddEditButton">
            <Property Name="Width" />
          </Object>
        </Item>
        <Item>
          <Object Name="MaterialListButton">
            <Property Name="Width" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Width>
  <NullValue Show="True" />
  <NullValueTitle>[Не выбрано]</NullValueTitle>
  <AutoCompleteMode>SmartSuggest</AutoCompleteMode>
  <ValueList>
    <DataConnection SourceDataConnection="MaterialShortPrimaryGetDataConnection">
      <Fields>
        <Field Name="MaterialId" />
        <Field Name="Title" />
      </Fields>
    </DataConnection>
  </ValueList>
  <Value>
    <Switch>
      <Case>
        <When>
          <Parameter Name="Edit" />
        </When>
        <Then>
          <DataConnection SourceDataConnection="OrderPositionPrimaryGetDataConnection">
            <Fields>
              <Field Name="MaterialId" />
            </Fields>
          </DataConnection>
        </Then>
      </Case>
      <Case />
    </Switch>
  </Value>
</MyObject>
```

{% endcode %}

Добавьте оставшиеся объекты на форму:

* Поле "Количество" (**QuantityNumericBox**) с признаками Minimum - 0, Increment - 5, DecimalPlaces - 2 и ThousandsSeparator - True. Заполняется на основе OrderPositionPrimaryGetDataConnection;
* Поле "Цена продажи за единицу" (**UnitPriceNumericBox**) с признаками Minimum - 0, Increment - 100, DecimalPlaces - 2 и ThousandsSeparator - True. Заполняется на основе OrderPositionPrimaryGetDataConnection;
* Поле "Категория материала" (**MaterialCategoryTextBox**) заполняется на основе MaterialSecondaryGetDataConnection. Только для чтения;
* Поле "Единица измерения материала" (**UnitTextBox**) заполняется на основе MaterialSecondaryGetDataConnection. Только для чтения;
* Поле "Итого" (**TotalPriceTextBox**) является произведением значений из полей QuantityNumericBox и UnitPriceNumericBox. Только для чтения.

У вас должна получиться форма вида:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FoTddW2ieU2tNKlqUKiIa%2Fimage.png?alt=media&#x26;token=a452593c-1738-4740-8859-bee838b56954" alt=""><figcaption></figcaption></figure>

Поправьте OrderPositionInsertSetDataConnection и OrderPositionUpdateSetDataConnection так, чтобы в параметры запросов передавались значения из *редактируемых* полей. Скорректируйте текст самих запросов. Не забудьте передавать параметр **OrderId** при сохранении новой позиции заказа.

Если попробуете создать заказ и добавить в него позицию, то поймаете ошибку: *null value in column "order\_id" violates not-null constraint* - так как в параметре OrderId с родительской формы приходит значение null. Ниже мы рассмотрим, как решить эту проблему, а пока продолжим работать с карточкой позиции заказа.

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

Первым делом создадим условие [ChangedCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/event_condition/changed_condition), которое отслеживает изменение значения выпадающего списка:

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

```html
<Condition Name="MaterialComboBoxChangedCondition" Type="ChangedCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="MaterialComboBox" />
    </Item>
  </Items>
</Condition>
```

{% endcode %}

Добавим в описание **MaterialComboBox** вложенный тэг [`<Change>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects#change) со значениями:

```xml
<Change User="True" Source="False" ValueSet="True" />
```

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

Создадим команду типа ValueSetCommand, в которой объекту UnitPriceNumericBox будем присваивать значение цены из выбранного в выпадающем списке ТМЦ:

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

```xml
<Command Name="UnitPriceNumericBoxValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="UnitPriceNumericBox">
    <DataConnection SourceDataConnection="MaterialSecondaryGetDataConnection">
      <Fields>
        <Field Name="UnitPrice" />
      </Fields>
    </DataConnection>
  </Object>
</Command>
```

{% endcode %}

Создадим `<Execution>` на условие **MaterialChangedCondition** с вызовом этой команды:

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

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="MaterialComboBoxChangedCondition" />
  </ConditionExpression>
  <Commands>
    <Command Name="UnitPriceNumericBoxValueSetCommand" />
  </Commands>
</Execution>
```

{% endcode %}

Также добавим вызов команды **UnitPriceNumericBoxValueSetCommand** в `<Execution>`, который отлавливает параметр Updated из команды **MaterialEditFormShowCommand**.

В случаях добавления ТМЦ или редактирования списка, мы вызываем команду MaterialComboBoxValueSetCommand, которая спровоцирует условие MaterialChangedCondition на рассылку события. Поэтому в эти `<Execution>` не будем добавлять вызов команды UnitPriceNumericBoxValueSetCommand.

## Самостоятельно

Реализуйте на форме списка товарно-материальных ценностей (TemplateMaterialList.xml) режим выбора. Этот режим должен работать только для таблицы ТМЦ. Список категорий остается без изменений.

## Сохранение вложенной сущности

### Проблема

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

![](https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FXIMB5FVR7ZOGVkL67BUu%2Fimage.png?alt=media\&token=56e38520-1e92-492b-8b0e-1bcab97ff282)

Как видно из сообщения об ошибке, значением параметра OrderId является **NULL**, а это противоречит ограничению `NOT NULL` в описании колонки **order\_id** в таблице template.orde&#x72;*\_*&#x70;osition. Иными словами, мы не можем сохранить позицию заказа в базе данных, пока не сохраним сам заказ и не получим его идентификатор.

Есть два варианта решения сложившейся ситуации:

### Решение 1. Сохранять с родительской формы

Суть решения заключается в том, что мы передаем данные на родительскую форму, пишем их в таблицу [DatabaseTable](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable) через ее свойства  AddRow, AddRows, UpdateRow, UpdateRows и DeleteRowsByIndices, как мы делали на форме списка городов. При попытке сохранить новый заказ выполняем последовательность из двух команд типа [SaveCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/save_command):

* первая для [SetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/set_dc) на вставку самой записи заказа, с возвратом на форму идентификатора нового заказа в результат команды SaveCommand;
* вторая для [DatabaseTableSetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/database_table_set_dc) для сохранения изменений позиций заказа, в котором используем результат первой команды.

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

Тогда появляется необходимость хранить на родительской форме (карточке заказа) данные всех вложенных сущностей и при сохранении в базу данных гарантировать целостность записей и следить за правильным соотношением идентификаторов. В таком случае будет удобнее работать с JSON-объектом, который нужно будет собирать вручную из различных источников и передавать на сервер единым запросом.&#x20;

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

### Решение 2. Паттерн Add/Edit

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

Для обозначения черновых записей в таблице в базе данных используется колонка added типа boolean со значением false. Когда сохраняются изменения, значение в этой колонке меняется на true.&#x20;

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

## Паттерн Add/Edit

### Подготовка

Добавим в таблицу заказов колонку added:

```sql
ALTER TABLE template."order"
  ADD COLUMN added boolean NOT NULL DEFAULT false;
```

Всем существующим заказам в колонке **added** присвоим значение **true**,

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

Поэтому нам необходимо с колонок **client\_id**, **order\_number** и **order\_date** убрать ограничения `NOT NULL`, вместо которых добавим проверку этих колонок на NULL при условии, что флаг **added** равен **true**:

```sql
ALTER TABLE template."order"
  ADD CONSTRAINT check_main_order_info CHECK (
      NOT added OR
      added AND
      client_id IS NOT NULL AND
      order_number IS NOT NULL AND
      order_date IS NOT NULL
  );
```

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

### Создание черновика

Для создания черновой записи заказа переделаем запрос **OrderInsertSqlQuery** и переименуем его в **EmptyOrderInsertSqlQuery**:

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

```xml
<SqlQuery Name="EmptyOrderInsertSqlQuery">
  <Text>
    INSERT INTO template.order (added)
    VALUES (false)
    RETURNING order_id;
  </Text>
</SqlQuery>
```

{% endcode %}

В запрос **OrderSelectSqlQuery** добавим условие `O.added AND`, чтобы исключить черновые заказы из таблицы на главной форме. А в запрос **OrderUpdateSqlQuery** добавим обновление значения в колонке added на true.

Перейдем на стартовую форму (TemplateStart.xml).&#x20;

Создадим SetDataConnection для этого запроса:

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

```xml
<DataConnection Name="EmptyOrderInsertSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <SqlQueries>
    <SqlQuery Name="EmptyOrderInsertSqlQuery" Type="Insert" />
  </SqlQueries>
</DataConnection>
```

{% endcode %}

Создадим команду **EmptyOrderInsertSaveCommand** типа SaveCommand, в которой будем вызывать EmptyOrderInsertSetDataConnection.

Переделаем команду OrderAddFormShowCommand, добавив параметр OrderId:

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

```xml
<Command Name="OrderAddFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateOrderEdit.xml</Xml>
  <Show Type="None" />
  <Parameters>
    <Parameter Name="OrderId" >
      <Command Name="EmptyOrderInsertSaveCommand" />
    </Parameter>
  </Parameters>
</Command>
```

{% endcode %}

На кнопку **OrderAddButton** перед командой OrderAddFormShowCommand добавим вызов новой команды EmptyOrderInsertSaveCommand.

Перейдем в файл карточки заказа (TemplateOrderEdit.xml), из которой удалим OrderInsertSetDataConnection и команды OrderInsertSaveCommand и OrderIdValueSetCommand.

Таким образом, команда SaveSequentialCommand примет вид:

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

```xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands>
    <Command Name="OrderUpdateSaveCommand" />
    <Command Name="UpdatedTrueValueSetCommand" />
    <Command Name="FormCloseCommand" />
  </Commands>
</Command>
```

{% endcode %}

### Удаление черновика

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

Перейдем в серверный xml-файл и добавим запрос:

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

```xml
<SqlQuery Name="EmptyOrderDeleteSqlQuery">
  <Text>
    DELETE FROM template.order_position OP
      USING template.order O
    WHERE
      OP.order_id = O.order_id AND
      NOT O.added AND
      OP.order_id = {OrderId};
    
    DELETE FROM template.order
    WHERE
      NOT added AND
      order_id = {OrderId};
  </Text>
</SqlQuery>
```

{% endcode %}

Вернемся в файл карточки заказа (TemplateOrderEdit.xml) и создадим соединение с данными EmptyOrderDeleteSetDataConnection для этого запроса и команду EmptyOrderDeleteSaveCommand. Скорректируем Execution на закрытие формы, добавив в него вызов команды на удаление. Таким образом, мы получим:

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

```xml
<Execution>
  <ConditionExpression>
    <Or>
      <And>
        <Or>
          <Condition Name="FormClosingCondition" />
          <Condition Name="EscapeKeyDownCondition" />
        </Or>
        <Not>
          <Condition Name="FormChangedEqualCondition" />
        </Not>
      </And>
      <Command Name="SaveOnCloseMessageBoxCommand" Parameter="No" />
      <Command Name="CloseOnCloseMessageBoxCommand" Parameter="Yes" />
    </Or>
  </ConditionExpression>
  <Commands>
    <If>
      <When>
        <Not>
          <Parameter Name="Edit" />
        </Not>
      </When>
      <Then>
        <Command Name="EmptyOrderDeleteSaveCommand" />
      </Then>
    </If>
    <Command Name="FormCloseCommand" />
  </Commands>
</Execution>
```

{% endcode %}

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

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

В уроке мы рассмотрели решения, позволяющие грамотно сохранять записи со сложной иерархической структурой. Одно из решений - паттерн Add/Edit - реализовали в нашем приложении в карточке заказа.

Суть паттерна Add/Edit заключается в создании черновой записи перед открытием карточки сущности на добавление, получение на форме ее идентификатора и использование его для сохранения вложенных сущностей.&#x20;

В следующем уроке рассмотрим выгрузку данных в текстовый документ.

## Ответы <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>lesson10-answer.zip</td><td><a href="https://wfsys.ru/download/wt_practice_desktop_answers/lesson10-answer.zip">https://wfsys.ru/download/wt_practice_desktop_answers/lesson10-answer.zip</a></td></tr></tbody></table>
