> 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_tree_in_the_table.md).

# Урок 12. Дерево в таблице

В этом уроке мы рассмотрим второй вариант работы с иерархической структурой данных в виде дерева, отображаемого в таблице DatabaseTable.&#x20;

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

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

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

## Список назначений платежей

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

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FgK8YGIWFj2YQMH0hMmJ5%2F01.png?alt=media&amp;token=5c17ffcc-2663-446f-a09d-3311dd6b09ff" alt=""><figcaption></figcaption></figure>

### База данных

#### Таблица для назначений платежей

Выполним скрипт на создание таблицы в базе данных:

```sql
CREATE SEQUENCE template.operation_id_seq;
CREATE TABLE template.operation
(
  operation_id integer NOT NULL DEFAULT nextval('template.operation_id_seq'::regclass),
  operation_category_id bigint,
  id_title character varying,
  title character varying NOT NULL,
  income boolean NOT NULL,
  archive boolean NOT NULL DEFAULT false,
  CONSTRAINT pk_operation_id PRIMARY KEY (operation_id),
  CONSTRAINT fk_operation_category_id FOREIGN KEY (operation_category_id)
      REFERENCES template.operation (operation_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE SET NULL,
  CONSTRAINT uniq_operation_id_title UNIQUE (id_title)
);
```

Обратите внимание на строчку:

```sql
CONSTRAINT uniq_operation_id_title UNIQUE (id_title)
```

В ней мы устанавливаем [ограничение уникальности](https://postgrespro.ru/docs/postgrespro/16/ddl-constraints#DDL-CONSTRAINTS-UNIQUE-CONSTRAINTS) значений в поле **id\_title**, которое будем использовать для указания *системных назначений платежей*.

А в поле **income** будем отмечать, является ли назначение платежей доходом (true) или расходом (false).

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

```sql
INSERT INTO template.operation(id_title, title, income)
VALUES ('OrderPaymentOperation', 'Оплата заказа', true);
```

#### Функция удаления записи

Создадим функцию для удаления назначения платежей:

```sql
CREATE OR REPLACE FUNCTION template.operation_try_delete(in_operation_id bigint)
  RETURNS character varying AS
$BODY$
DECLARE
BEGIN
  IF (SELECT NOT EXISTS(SELECT * FROM template.operation WHERE operation_id = in_operation_id)) THEN
    RETURN NULL;
  END IF;

  IF (SELECT EXISTS(SELECT * FROM template.operation WHERE operation_category_id = in_operation_id)) THEN
    RETURN 'parent';
  END IF;

  IF (SELECT used from template.is_used('operation', 'operation_id', ARRAY['operation'], ARRAY[]::text[], in_operation_id)) THEN
    UPDATE template.operation
    SET archive = TRUE
    WHERE operation_id = in_operation_id AND NOT archive;

    RETURN 'used';
  ELSE
    DELETE FROM template.operation
    WHERE operation_id = in_operation_id;
 
    RETURN NULL;
  END IF;
END;
$BODY$
  LANGUAGE plpgsql;
```

При удалении записи необходимо учитывать, что назначение может быть родительским для других назначений платежей, и использовать другую логику для обработки таких ситуаций. Поэтому функция возвращает тип **character varying**, в котором может быть одно из значений:&#x20;

* parent - запись является родительской;
* used - запись используется в программе и перемещена в архив;
* NULL - запись удалена, либо ее нет в базе данных.

#### Вспомогательная таблица

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

Выполним скрипт:

```sql
CREATE SEQUENCE template.cash_id_seq;
CREATE TABLE template.cash
(
  cash_id bigint NOT NULL DEFAULT nextval('template.cash_id_seq'::regclass),
  cash_date timestamp without time zone NOT NULL,
  account_id smallint NOT NULL,
  operation_id integer NOT NULL,
  summ numeric NOT NULL,
  date_created timestamp without time zone NOT NULL DEFAULT now(),
  deleted boolean NOT NULL DEFAULT false,
  date_deleted timestamp without time zone,
  CONSTRAINT pk_cash_id PRIMARY KEY (cash_id),
  CONSTRAINT fk_operation_id FOREIGN KEY (operation_id)
      REFERENCES template.operation (operation_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);
```

Поле account\_id будет хранить идентификатор счета. Таблицу и формы редактирования списка счетов создадим позже в этом уроке, там же создадим внешний ключ для этого поля.&#x20;

Отлично! Теперь можем заняться формой для редактирования списка операций.

### Форма списка

На главной форме (TemplateStart.xml) в меню добавим пункт **Списки -> Назначения платежей...**, по которому будет открываться новая форма (TemplateOperationList.xml).

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FR4n1BW4ryPTrQ9bMVXR0%2F02.png?alt=media&amp;token=46ee813a-7bd4-4630-b2db-b0b5de46d8d1" alt=""><figcaption></figcaption></figure>

Создадим необходимые формы для списка назначений платежей с помощью шаблона ArchiveList:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FbBf9L3MW9v4eWwOl47dm%2Fimage.png?alt=media&amp;token=880a8b00-9462-467f-9412-720b7dff4d58" alt=""><figcaption></figcaption></figure>

Перейдем в файл описания работы серверной части приложения (Template.xml) и скорректируем запрос **OperationSelectSqlQuery**:

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

```xml
<SqlQuery Name="OperationSelectSqlQuery">
  <Text>  
    SELECT
      true AS "Expand",
      -2::bigint AS "OperationId",
      NULL::bigint AS "OperationCategoryId",
      'ДОХОД' AS "Title",
      false AS "System",
      false AS "Archive"
    
    UNION ALL
    
    SELECT
      true AS "Expand",
      -1::bigint,
      NULL::bigint,
      'РАСХОД',
      false,
      false
    
    UNION ALL
    
    SELECT
      true AS "Expand",
      O.operation_id AS "OperationId",
      COALESCE(O.operation_category_id, CASE WHEN income THEN -2 ELSE -1 END) AS "OperationCategoryId",
      O.title AS "Title",
      O.id_title NOTNULL AS "System",
      O.archive AS "Archive"
    FROM template.operation O;
  </Text>
</SqlQuery>
```

{% endcode %}

Назначения платежей на форме будут представлены в виде дерева и разбиты на два блока, в которых записи "ДОХОД" и "РАСХОД" будут корневыми категориями.&#x20;

Поле **Expand** будет отвечать за разворачивание/сворачивание узлов дерева.

#### Список назначений платежей

Перейдем в файл TemplateOperationList.xml. В описание тэга `<Appearance>` добавим шрифт для корневых категорий:

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

```xml
<FontStyle Name="TitleBoldFont" Font="Segoe UI" Size="9" Bold="True" />
```

{% endcode %}

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

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

```xml
<Color Name="Yellow" Red="249" Green="242" Blue="165" Alpha="255" />
<Color Name="LightYellow" Red="255" Green="255" Blue="213" Alpha="255" />
```

{% endcode %}

Заменим в **OperationPrimaryGetDataConnection** список полей:

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

```xml
<DataConnection Name="OperationPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="OperationSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="Expand" />
      <Field Name="OperationId" />
      <Field Name="OperationCategoryId" />
      <Field Name="Title" />
      <Field Name="System" />
      <Field Name="Archive" />
    </Fields>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

Для создания дерева записей в таблице DatabaseTable будем использовать [TreeGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc), который на основе данных из OperationPrimaryGetDataConnection будет строить дерево.

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

```xml
<DataConnection Name="OperationTreeGetDataConnection" Type="TreeGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="OperationPrimaryGetDataConnection">
    <Fields>
      <Field Name="OperationId" />
      <Field Name="Title" />
      <Field Name="Expand" />
      <Field Name="OperationCategoryId" />
      <Field Name="System" />
      <Field Name="Archive" />
    </Fields>
  </SourceDataConnection>
  <RelationshipDataConnection Name="OperationPrimaryGetDataConnection">
    <Fields>
      <Field Name="OperationId" />
      <Field Name="OperationCategoryId" />
    </Fields>
  </RelationshipDataConnection>
  <AdditionalColumns>
    <HasChildrenColumn Name="HasChildren" />
    <StateColumn Name="State" CloseState="+" OpenState="-" />
  </AdditionalColumns>
  <Filter FilterByNullValue="False">
    <Field NativeName="Archive" />
    <Value>
      <Object Name="ArchiveFilterComboBox" />
    </Value>
    <DataType Type="BooleanDataType" />
  </Filter>
</DataConnection>
```

{% endcode %}

В тэге [`<SourceDataConnection>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc#source_data_connection) указываем источник данных для дерева. Первых три поля являются обязательными и их порядок строго определен. Первое поле должно соответствовать идентификатору элемента, второе - его отображаемому значению, третье - его состоянию (свернут/развернут узел дерева, если он имеет дочерние элементы).

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

В тэге [`<RelationshipDataConnection>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc#relationship_data_connection) указываем таблицу с двумя колонками: в первой колонке должен быть идентификатор элемента, а во второй - идентификатор родительского элемента. На основе этой таблицы TreeGetDataConnection построит дерево.

В тэге [`<AdditionalColumns>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc#additional_columns) задаем имена для дополнительных колонок: одно для колонки с признаком наличия дочерних элементов (HasChildren), другое для колонки отображения значения состояния узла (State).

Укажем **OperationTreeGetDataConnection** в качестве источника данных для таблицы **OperationDatabaseTable**.

Заменим тэг `<Columns>` в описании таблицы кодом, в котором описаны все колонки из OperationPrimaryGetDataConnection и дополнительные колонки из OperationTreeGetDataConnection:

```xml
<Columns>
  <Column Name="OperationId" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
  </Column>
  <Column Name="OperationCategoryId" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
  </Column>
  <Column Name="State" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Title />
    <Width>30</Width>
    <AutoSizeMode Value="None" />
    <ManagementMode>NotAllowed</ManagementMode>
    <Alignment Value="MiddleCenter" />
    <AllowSort Value="False" />
  </Column>
  <Column Name="Title" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Title>Наименование</Title>
    <MinimumWidth>100</MinimumWidth>
    <AutoSizeMode Value="Fill" />
    <AllowSort Value="False" />
  </Column>
  <Column Name="System" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
  </Column>
  <Column Name="Archive" Type="DatabaseTableColumnCheckBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
    <DataType Type="BooleanDataType" />
  </Column>
  <Column Name="Expand" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
  </Column>
  <Column Name="HasChildren" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
    <Visible>False</Visible>
  </Column>
</Columns>
```

Добавим в тэг `<Formatting>` таблицы OperationDatabaseTable условия форматирования строк:

```xml
<BackColor Name="LightYellow">
  <Expression>HasChildren AND OperationId <![CDATA[>]]> 0</Expression>
</BackColor>
<BackColor Name="Yellow">
  <Expression>OperationId <![CDATA[<]]> 0</Expression>
</BackColor>
<FontStyle Name="TitleBoldFont">
  <Expression>OperationId <![CDATA[<]]> 0</Expression>
</FontStyle>
```

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FihQqAiPmAI0IvAORaZ3h%2Fimage.png?alt=media&amp;token=ac738ed8-a210-4df6-9844-cd523fc5dece" alt=""><figcaption></figcaption></figure>

Отлично! Переключимся на форму редактирования назначения платежей, а затем вернемся на форму списка и доделаем дерево.

### Карточка сущности

В файле TemplateOperationEdit.xml создайте необходимые объекты, чтобы у вас получилась форма вида:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fj1vaqbvrFbO0z6JYqv7t%2Fimage.png?alt=media&amp;token=4eee6a0d-abc9-4b86-92fe-1e1fd66054c4" alt=""><figcaption></figcaption></figure>

Добавьте на форму параметр **OperationCategoryId**.

Переделайте **OperationPrimaryGetDataConnection**, добавив в SQL-запрос поля:

* OperationCategoryId - для получения значений используйте то же выражение, которое используем в построении дерева на форме списка назначений платежей (OperationSelectSqlQuery);
* IsIncome и IsSystem - они нам понадобятся для проверки в случае изменения типа (доход или расход) на противоположный у системных назначений и у назначений, которые используются в программе.

#### Список категорий

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

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

```xml
<SqlQuery Name="OperationCategorySelectSqlQuery">
  <Text>
    WITH RECURSIVE tree (operation_id, operation_category_id, title, id_title, income, path, archive) AS (
      SELECT -2::bigint, NULL::bigint, 'ДОХОД', NULL, true, ARRAY[-2]::bigint[], false
    
      UNION
    
      SELECT -1::bigint, NULL::bigint, 'РАСХОД', NULL, false, ARRAY[-1]::bigint[], false
    
      UNION
    
      SELECT operation_id  AS operation_id, operation_category_id, title, id_title, income, ARRAY[CASE WHEN income THEN -2 ELSE -1 END, operation_id]::bigint[] AS path, archive
      FROM _t
    
      UNION
    
      SELECT o2.operation_id  AS operation_id, o2.operation_category_id, o2.title, o2.id_title, o2.income, tree.path || ARRAY[o2.operation_id]::bigint[] AS path, o2.archive
      FROM 
        _t, _o o2
        INNER JOIN tree ON tree.operation_id = o2.operation_category_id
    ), _t AS (
      SELECT  *
      FROM _o
      WHERE operation_category_id IS NULL 
    ), _o AS(
      SELECT  *
      FROM template.operation o
      WHERE operation_id IS DISTINCT FROM {OperationId} AND id_title IS NULL
    )
    SELECT
      T.operation_id AS "OperationId",
      CASE WHEN array_length(T.path, 1) > 1 THEN COALESCE(repeat('  ', (array_length(T.path, 1)-1)*2) || ' ', '') ELSE '' END || T.title AS "Title",
      income AS "IsIncome"
    FROM tree T
    LEFT JOIN
    (
      WITH RECURSIVE current_tree (operation_id, operation_category_id) AS(
        SELECT
          operation_id,
          operation_category_id
        FROM
          template.operation O
        WHERE
          {OperationCategoryId} IS NOT NULL AND operation_id = {OperationCategoryId}
    
        UNION
    
        SELECT
          O.operation_id,
          COALESCE(O.operation_category_id, CASE WHEN income THEN -2 ELSE -1 END)::bigint
        FROM
          template.operation O
          JOIN current_tree T ON (O.operation_id = T.operation_category_id)
      ), t AS (
        SELECT * FROM current_tree
    
        UNION
    
        SELECT -2, NULL WHERE EXISTS (SELECT * FROM current_tree WHERE operation_category_id = -2)
    
        UNION
    
        SELECT -1, NULL  WHERE EXISTS (SELECT * FROM current_tree WHERE operation_category_id = -1)
      ) SELECT * FROM t) T2 ON (T.operation_id = T2.operation_id)
    WHERE
      (NOT archive OR T2.operation_id IS NOT NULL)
    ORDER BY T.path;
  </Text>
</SqlQuery>
```

{% endcode %}

Вернемся в файл формы редактирования назначения платежей (TemplateOperationEdit.xml).

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

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

```xml
<DataConnection Name="OperationCategoryPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <SqlQuery Name="OperationCategorySelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="OperationId" />
      <Field Name="Title" />
      <Field Name="IsIncome" />
    </Fields>
    <Parameters>
      <Parameter NativeName="OperationId" RefreshQuery="False">
        <Value>
          <Parameter Name="OperationId" />
        </Value>
      </Parameter>
      <Parameter NativeName="OperationCategoryId" RefreshQuery="False">
        <Value>
          <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
            <Fields>
              <Field Name="OperationCategoryId" />
            </Fields>
          </DataConnection>
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

Укажем новый OperationCategoryPrimaryGetDataConnection в тэге `<ValueList>` выпадающего списка поля "Категория", а в качестве значения тэга \<Value> того же объекта напишем конструкцию:

```xml
<Switch>
  <Case>
    <When>
      <Parameter Name="Edit" />
    </When>
    <Then>
      <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
        <Fields>
          <Field Name="OperationCategoryId" />
        </Fields>
      </DataConnection>
    </Then>
  </Case>
  <Case>
    <Parameter Name="OperationCategoryId" />
  </Case>
</Switch>
```

#### Изменение категории назначения платежей

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

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

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

```xml
<DataConnection Name="OperationCategorySecondaryGetDataConnection" Type="SecondaryGetDataConnection" Assembly="DataConnections">
  <SourceDataConnection Name="OperationCategoryPrimaryGetDataConnection" />
  <Filter>
    <Field NativeName="OperationId" />
    <Value>
      <Object Name="CategoryComboBox" />
    </Value>
    <DataType Type="IntegerDataType" />
  </Filter>
</DataConnection>
```

{% endcode %}

Создадим условие проверки идентификатора выбранной категории, чтобы отсекать  служебные записи "ДОХОД" (OperationId = -2) и "РАСХОД" (OperationId = -1):

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

```xml
<Condition Name="CategoryIdGreater0Condition" Type="GreaterCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="CategoryComboBox" />
    </Item>
    <Item>0</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

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

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

```xml
<Condition Name="OperationIsSystemEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
        <Fields>
          <Field Name="IsSystem" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>True</Item>
  </Items>
  <DataType Type="BooleanDataType" />
</Condition>
```

{% endcode %}

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

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

```xml
<Condition Name="OperationTypeAndCategoryTypeNotEqualCondition" Type="NotEqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
        <Fields>
          <Field Name="IsIncome" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>
      <DataConnection SourceDataConnection="OperationCategorySecondaryGetDataConnection">
        <Fields>
          <Field Name="IsIncome" />
        </Fields>
      </DataConnection>
    </Item>
  </Items>
  <DataType Type="BooleanDataType" />
</Condition>
```

{% endcode %}

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

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

```xml
<DataConnection Name="OperationIsUsedPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <ManualLoad>True</ManualLoad>
  <SqlQuery Name="OperationIsUsedSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="IsUsed" />
    </Fields>
    <Parameters>
      <Parameter NativeName="OperationId" RefreshQuery="False">
        <Value>
          <Parameter Name="OperationId" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

<details>

<summary>OperationIsUsedSelectSqlQuery</summary>

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

```xml
<SqlQuery Name="OperationIsUsedSelectSqlQuery">
  <Text>
    SELECT template.operation_is_used_in_view_of_child({OperationId}::bigint) AS "IsUsed";
  </Text>
</SqlQuery>
```

{% endcode %}

Где функция **template.operation\_is\_used\_in\_view\_of\_child(bigint)** имеет вид:

```sql
CREATE OR REPLACE FUNCTION template.operation_is_used_in_view_of_child(in_operation_id bigint)
  RETURNS boolean AS
$BODY$
DECLARE
  _operation_id_array bigint[];
  _is_system boolean;
BEGIN
  WITH RECURSIVE operation_tree(operation_id, id_title) AS(
    SELECT
      operation_id,
      id_title
    FROM template.operation
    WHERE operation_id = in_operation_id

    UNION ALL

    SELECT
      O.operation_id,
      O.id_title
    FROM
      template.operation O
      JOIN operation_tree T ON (O.operation_category_id = T.operation_id)
  )
  SELECT
    array_agg(operation_id),
    bool_or(id_title IS NOT NULL)
  INTO _operation_id_array, _is_system
  FROM operation_tree;

  RETURN (_is_system OR EXISTS(SELECT * FROM template.cash C WHERE C.operation_id = ANY(_operation_id_array) AND NOT C.deleted));
END;
$BODY$
  LANGUAGE plpgsql;
```

</details>

Создайте самостоятельно команду OperationIsUsedDataConnectionRefreshCommand для обновления OperationIsUsedPrimaryGetDataConnection.

Добавим условие EqualCondition для проверки поля IsUsed:

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

```xml
<Condition Name="OperationIsUsedEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="OperationIsUsedPrimaryGetDataConnection">
        <Fields>
          <Field Name="IsUsed" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>True</Item>
  </Items>
  <DataType Type="BooleanDataType" />
</Condition>
```

{% endcode %}

Создадим PrimaryGetDataConnection для проверки, является ли выбранное в качестве категории назначение платежей листом, т.е. не имеет дочерних назначений:

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

```xml
<DataConnection Name="OperationIsLeafPrimaryGetDataConnection" Type="PrimaryGetDataConnection" Assembly="DataConnections">
  <ManualLoad>True</ManualLoad>
  <SqlQuery Name="OperationIsLeafSelectSqlQuery" Type="Select">
    <Workflow Name="Template" />
    <Fields>
      <Field Name="Title" />
      <Field Name="IsLeaf" />
      <Field Name="IsUsed" />
    </Fields>
    <Parameters>
      <Parameter NativeName="OperationId" RefreshQuery="False">
        <Value>
          <Object Name="CategoryComboBox" />
        </Value>
      </Parameter>
    </Parameters>
  </SqlQuery>
</DataConnection>
```

{% endcode %}

<details>

<summary>OperationIsLeafSelectSqlQuery</summary>

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

```xml
<SqlQuery Name="OperationIsLeafSelectSqlQuery">
  <Text>
    SELECT
      operation_id  AS "OperationId",
      title AS "Title",
      income AS "IsIncome",
      NOT EXISTS(SELECT * FROM template.operation WHERE operation_category_id = O.operation_id) AS "IsLeaf",
      EXISTS(SELECT * FROM template.cash C WHERE C.operation_id = O.operation_id) AS "IsUsed"
    FROM template.operation O
    WHERE operation_id = {OperationId};
  </Text>
</SqlQuery>
```

{% endcode %}

</details>

Создайте самостоятельно команду OperationIsLeafDataConnectionRefreshCommand для обновления OperationIsLeafPrimaryGetDataConnection.

Добавим условия EqualCondition для проверки полей IsLeaf и IsUsed выбранной категории:

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

```xml
<Condition Name="OperationCategoryIsLeafEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="OperationIsLeafPrimaryGetDataConnection">
        <Fields>
          <Field Name="IsLeaf" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>True</Item>
  </Items>
  <DataType Type="BooleanDataType" />
</Condition>

<Condition Name="OperationCategoryIsUsedEqualCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <DataConnection SourceDataConnection="OperationIsLeafPrimaryGetDataConnection">
        <Fields>
          <Field Name="IsUsed" />
        </Fields>
      </DataConnection>
    </Item>
    <Item>True</Item>
  </Items>
  <DataType Type="BooleanDataType" />
</Condition>
```

{% endcode %}

Создадим команду MessageBoxCommand для уведомления пользователя о невозможности изменения типа редактируемого назначения платежей:

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

```xml
<Command Name="OperationChangedIsIncomeMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Сохранение</Caption>
  <Text>
    <String>
      <Format>При изменении категории будет изменен тип назначения «{0}» с «{1}»  на «{2}», что недопустимо, т.к. назначение «{0}» {3}</Format>
      <Items>
        <Item>
          <Object Name="TitleTextBox" />
        </Item>
        <Item>
          <Switch>
            <Case>
              <When>
                <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
                  <Fields>
                    <Field Name="IsIncome" />
                  </Fields>
                </DataConnection>
              </When>
              <Then>Дохода</Then>
            </Case>
            <Case>Расхода</Case>
          </Switch>
        </Item>
        <Item>
          <Switch>
            <Case>
              <When>
                <DataConnection SourceDataConnection="OperationPrimaryGetDataConnection">
                  <Fields>
                    <Field Name="IsIncome" />
                  </Fields>
                </DataConnection>
              </When>
              <Then>Расход</Then>
            </Case>
            <Case>Доход</Case>
          </Switch>
        </Item>
        <Item>
          <Switch>
            <Case>
              <When>
                <Condition Name="OperationIsSystemEqualCondition" />
              </When>
              <Then>является системным.</Then>
            </Case>
            <Case>
              <When>
                <Condition Name="OperationIsUsedEqualCondition" />
              </When>
              <Then>используется в программе.</Then>
            </Case>
          </Switch>
        </Item>
      </Items>
    </String>
  </Text>
  <Icon Type="Warning" />
  <Buttons Type="Ok" />
</Command>
```

{% endcode %}

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

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

```xml
<Command Name="OperationIsLeafMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Сохранение</Caption>
  <Text>
    <String>
      <Format>При добавлении назначения платежа «{0}» в категорию «{1}» все операции, привязанные к назначению «{1}», будут привязаны к назначению «{0}». Продолжить?</Format>
      <Items>
        <Item>
          <Object Name="TitleTextBox" />
        </Item>
        <Item>
          <DataConnection SourceDataConnection="OperationIsLeafPrimaryGetDataConnection">
            <Fields>
              <Field Name="Title" />
            </Fields>
          </DataConnection>
        </Item>
      </Items>
    </String>
  </Text>
  <Icon Type="Question" />
  <Buttons Type="YesNo" />
</Command>
```

{% endcode %}

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

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

```xml
<Command Name="SaveSequentialCommand" Type="SequentialCommand" Assembly="Commands">
  <Commands Lock="True">
    <If>
      <When>
        <And>
          <Parameter Name="Edit" />
          <Not>
            <Condition Name="OperationIsSystemEqualCondition" />
          </Not>
          <Condition Name="OperationTypeAndCategoryTypeNotEqualCondition" />
        </And>
      </When>
      <Then>
        <Command Name="OperationIsUsedDataConnectionRefreshCommand" />
      </Then>
    </If>
    <If>
      <When>
        <And>
          <Parameter Name="Edit" />
          <Or>
            <Condition Name="OperationIsSystemEqualCondition" />
            <Condition Name="OperationIsUsedEqualCondition" />
          </Or>
          <Condition Name="OperationTypeAndCategoryTypeNotEqualCondition" />
        </And>
      </When>
      <Then>
        <Command Name="OperationChangedIsIncomeMessageBoxCommand" />
      </Then>
      <Else>
        <If>
          <When>
            <Condition Name="CategoryIdGreater0Condition" />
          </When>
          <Then>
            <Command Name="OperationIsLeafDataConnectionRefreshCommand" />
            <If>
              <When>
                <And>
                  <Condition Name="OperationCategoryIsLeafEqualCondition" />
                  <Condition Name="OperationCategoryIsUsedEqualCondition" />
                </And>
              </When>
              <Then>
                <Command Name="OperationIsLeafMessageBoxCommand" />
              </Then>
            </If>
          </Then>
        </If>
        <If>
          <When>
            <Or>
              <Not>
                <Condition Name="CategoryIdGreater0Condition" />
              </Not>
              <Not>
                <And>
                  <Condition Name="OperationCategoryIsLeafEqualCondition" />
                  <Condition Name="OperationCategoryIsUsedEqualCondition" />
                </And>
              </Not>
              <Command Name="OperationIsLeafMessageBoxCommand" Parameter="Yes" />
            </Or>
          </When>
          <Then>
            <If>
              <When>
                <Parameter Name="Edit" />
              </When>
              <Then>
                <Command Name="OperationUpdateSaveCommand" />
              </Then>
              <Else>
                <Command Name="OperationInsertSaveCommand" />
                <Command Name="OperationIdValueSetCommand" />
              </Else>
            </If>
            <Command Name="UpdatedTrueValueSetCommand" />
            <Command Name="FormCloseCommand" />
          </Then>
        </If>
      </Else>
    </If>
  </Commands>
</Command>
```

{% endcode %}

В команде в первым блоке `<If>` проверяем, не является ли редактируемое назначение платежей системным, и совпадает ли его тип с типом выбранной категории. Если тип не совпадает и назначение не системное, то отправляем запрос на сервер для проверки использования назначения платежа.

Во втором блоке `<If>` проверяем, является ли редактируемое назначение платежей системным или оно используется в программе и его тип не совпадает с типом выбранной категории. Если условие выполняется, то предупреждаем пользователя о смене типа назначения платежей при изменении его категории. Иначе выполняется большой вложенный блок проверок, в котором сначала проверяется, является ли редактируемое назначение платежей листом и выводится соответствующий вопрос пользователю. Затем идет завершающая проверка на возможность сохранения изменений.

#### Сохранение назначения платежей

Добавим параметры в OperationInsertSetDataConnection:

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

```xml
<DataConnection Name="OperationInsertSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <SqlQueries>
    <SqlQuery Name="OperationInsertSqlQuery" Type="Insert" />
  </SqlQueries>
  <Parameters>
    <Parameter NativeName="OperationCategoryId">
      <Value>
        <Switch>
          <Case>
            <When>
              <Condition Name="CategoryIdGreater0Condition" />
            </When>
            <Then>
              <Object Name="CategoryComboBox" />
            </Then>
          </Case>
        </Switch>
      </Value>
    </Parameter>
    <Parameter NativeName="Title">
      <Value>
        <Object Name="TitleTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="IsIncome">
      <Value>
        <DataConnection SourceDataConnection="OperationCategorySecondaryGetDataConnection">
          <Fields>
            <Field Name="IsIncome" />
          </Fields>
        </DataConnection>
      </Value>
    </Parameter>
    <Parameter NativeName="OperationCategoryIsLeaf">
      <Value>
        <And>
          <Condition Name="CategoryIdGreater0Condition" />
          <Condition Name="OperationCategoryIsLeafEqualCondition" />
        </And>
      </Value>
    </Parameter>
  </Parameters>
</DataConnection>
```

{% endcode %}

<details>

<summary>OperationInsertSqlQuery</summary>

<pre class="language-xml" data-title="Template.xml"><code class="lang-xml">&#x3C;SqlQuery Name="OperationInsertSqlQuery">
  &#x3C;Text>
    INSERT INTO template.operation (
      title,
      operation_category_id,
      income
    )
    VALUES (
      {Title},
      {OperationCategoryId},
      {IsIncome}
    )
    RETURNING operation_id;
    
<strong>    UPDATE template.cash
</strong>    SET
      operation_id = currval('template.operation_id_seq')
    WHERE
      {OperationCategoryIsLeaf} AND
      operation_id = {OperationCategoryId};
  &#x3C;/Text>
&#x3C;/SqlQuery>
</code></pre>

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

В статье [Функции nextval и currval](https://wfsys.gitbook.io/wt-knowledge-base/sql/function_nextval_currval) в Базе знаний дана информация о способах получения значения последовательности.

</details>

Добавим параметры в OperationUpdateSetDataConnection:

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

```xml
<DataConnection Name="OperationUpdateSetDataConnection" Type="SetDataConnection" Assembly="DataConnections">
  <Workflow Name="Template" />
  <SqlQueries>
    <SqlQuery Name="OperationUpdateSqlQuery" Type="Update" />
  </SqlQueries>
  <Parameters>
    <Parameter NativeName="OperationId">
      <Value>
        <Parameter Name="OperationId" />
      </Value>
    </Parameter>
    <Parameter NativeName="OperationCategoryId">
      <Value>
        <Switch>
          <Case>
            <When>
              <Condition Name="CategoryIdGreater0Condition" />
            </When>
            <Then>
              <Object Name="CategoryComboBox" />
            </Then>
          </Case>
        </Switch>
      </Value>
    </Parameter>
    <Parameter NativeName="Title">
      <Value>
        <Object Name="TitleTextBox" />
      </Value>
    </Parameter>
    <Parameter NativeName="IsIncome">
      <Value>
        <DataConnection SourceDataConnection="OperationCategorySecondaryGetDataConnection">
          <Fields>
            <Field Name="IsIncome" />
          </Fields>
        </DataConnection>
      </Value>
    </Parameter>
    <Parameter NativeName="OperationCategoryIsLeaf">
      <Value>
        <And>
          <Condition Name="CategoryIdGreater0Condition" />
          <Condition Name="OperationCategoryIsLeafEqualCondition" />
        </And>
      </Value>
    </Parameter>
  </Parameters>
</DataConnection>
```

{% endcode %}

<details>

<summary>OperationUpdateSqlQuery</summary>

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

```xml
<SqlQuery Name="OperationUpdateSqlQuery">
  <Text>
    UPDATE template.operation
    SET
      title = CASE WHEN id_title IS NULL THEN {Title} ELSE title END,
      operation_category_id = {OperationCategoryId},
      income = {IsIncome}
      WHERE operation_id = {OperationId};

    SELECT template.operation_update_child({OperationId}::bigint);

    UPDATE template.cash
    SET
      operation_id = {OperationId}
    WHERE
      {OperationCategoryIsLeaf} AND
      operation_id = {OperationCategoryId};
  </Text>
</SqlQuery>
```

{% endcode %}

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

Здесь помимо обновления данных самого назначения платежа необходимо вызвать функцию **template.operation\_update\_child(bigint)** на изменения типа назначения для всех дочерних элементов.

```sql
CREATE OR REPLACE FUNCTION template.operation_update_child(in_operation_id bigint)
  RETURNS void AS
$BODY$
DECLARE
  _child record;
  _parent record;
BEGIN
  SELECT * INTO _parent FROM template.operation WHERE operation_id = in_operation_id;

  FOR _child IN (SELECT * FROM template.operation WHERE operation_category_id = in_operation_id)
  LOOP
    UPDATE template.operation
    SET income = _parent.income
    WHERE operation_id = _child.operation_id;

    PERFORM template.operation_update_child(_child.operation_id);
  END LOOP;
END;
$BODY$
  LANGUAGE plpgsql;
```

</details>

Отлично! С карточкой редактирования назначения платежей мы закончили, теперь вернемся в файл списка назначений (TemplateOperationList.xml) и продолжим работать с ним.

### Добавление записи

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

Создадим условие EqualCondition проверки признака системного назначения платежей:

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

```xml
<Condition Name="SelectedOperationIsNotSystemCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="OperationDatabaseTable">
        <Property Name="SelectedRowCellValueByColumnName">
          <Parameters>
            <Parameter Name="ColumnName">System</Parameter>
          </Parameters>
        </Property>
      </Object>
    </Item>
    <Item>False</Item>
  </Items>
</Condition>
```

{% endcode %}

Скорректируем синтаксис кнопки создания назначения, добавив в тэг `<Enabled>` условия проверки, является ли выбранная запись системной или архивной:

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

```xml
<MyObject Name="OperationAddButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Object Name="OperationDatabaseTable">
      <Property Name="Top" />
    </Object>
  </Top>
  <Left>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="OperationDatabaseTable">
            <Property Name="Right" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Left>
  <Width>40</Width>
  <Height>40</Height>
  <TabIndex>3</TabIndex>
  <Hint>Добавить запись...</Hint>
  <BackgroundImage>Images\24x24\plus.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <Enabled>
    <Or>
      <Not>
        <Condition Name="OperationSelectedCondition" />
      </Not>
      <And>
        <Condition Name="SelectedOperationIsNotSystemCondition" />
        <Not>
          <Condition Name="SelectedOperationIsArchiveCondition" />
        </Not>
      </And>
    </Or>
  </Enabled>
  <Commands>
    <Command Name="OperationAddFormShowCommand" />
  </Commands>
  <DisabledMode>True</DisabledMode>
  <DisabledText>
    <Switch>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIsNotSystemCondition" />
          </Not>
        </When>
        <Then>Выбранное назначение платежа является системным и не может быть выбрано в качества категории при добавлении назначения.</Then>
      </Case>
      <Case>
        <When>
          <Condition Name="SelectedOperationIsArchiveCondition" />
        </When>
        <Then>Выбранное назначение платежа является архивным и не может быть выбрано в качества категории при добавлении назначения.</Then>
      </Case>
    </Switch>
  </DisabledText>
</MyObject>
```

{% endcode %}

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

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

```xml
<Command Name="OperationAddFormShowCommand" Type="FormShowCommand" Assembly="Commands">
  <Xml Type="Path">TemplateOperationEdit.xml</Xml>
  <Show Type="None" />
  <Multiple Allow="True" />
  <Parameters>
    <Parameter Name="OperationCategoryId">
      <Object Name="OperationDatabaseTable">
        <Property Name="SelectedRowCellValueByColumnName">
          <Parameters>
            <Parameter Name="ColumnName">OperationId</Parameter>
          </Parameters>
        </Property>
      </Object>
    </Parameter>
  </Parameters>
</Command>
```

{% endcode %}

Отлично! Откройте приложение и создайте несколько назначений платежа.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FMuYCR7BJ9VYbwCsbw4bm%2Fimage.png?alt=media&amp;token=c856614e-cefd-471b-a644-e3aa2845b246" alt=""><figcaption></figcaption></figure>

### Редактирование записи

Помимо запрета на редактирование архивных записей, мы должны ограничить редактирование служебных строк "ДОХОД" и "РАСХОД".&#x20;

Создадим условие, где будем проверять, что идентификатор выбранной записи больше нуля. Служебные строки имеют отрицательные идентификаторы: запись "ДОХОД" имеет идентификатор -2, а запись "РАСХОД" - идентификатор -1.

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

```xml
<Condition Name="SelectedOperationIdGreater0Condition" Type="GreaterCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="OperationDatabaseTable">
        <Property Name="SelectedRowCellValueByColumnName">
          <Parameters>
            <Parameter Name="ColumnName">OperationId</Parameter>
          </Parameters>
        </Property>
      </Object>
    </Item>
    <Item>0</Item>
  </Items>
  <DataType Type="IntegerDataType" />
</Condition>
```

{% endcode %}

Скорректируем синтаксис кнопки редактирования назначения платежей, добавив в тэг `<Enabled>` ограничение для служебных строк:

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

```xml
<MyObject Name="OperationEditButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="OperationAddButton">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Object Name="OperationAddButton">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="OperationAddButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <Height>
    <Object Name="OperationAddButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <TabIndex>4</TabIndex>
  <Hint>Редактировать запись...</Hint>
  <BackgroundImage>Images\24x24\pencil.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <Enabled>
    <And>
      <Condition Name="OperationSelectedCondition" />
      <Not>
        <Condition Name="SelectedOperationIsArchiveCondition" />
      </Not>
      <Condition Name="SelectedOperationIdGreater0Condition" />
    </And>
  </Enabled>
  <Commands>
    <Command Name="OperationEditFormShowCommand" />
  </Commands>
  <DisabledMode>True</DisabledMode>
  <DisabledText>
    <Switch>
      <Case>
        <When>
          <Not>
            <Condition Name="OperationSelectedCondition" />
          </Not>
        </When>
        <Then>Выберите запись для редактирования.</Then>
      </Case>
      <Case>
        <When>
          <Condition Name="SelectedOperationIsArchiveCondition" />
        </When>
        <Then>Для редактирования восстановите запись из архива.</Then>
      </Case>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIdGreater0Condition" />
          </Not>
        </When>
        <Then>Выбранная запись является служебной и не может быть отредактирована.</Then>
      </Case>
    </Switch>
  </DisabledText>
</MyObject>
```

{% endcode %}

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

### Сворачивание/разворачивание узлов дерева

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

С помощью условия [CellClickCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/event_condition/cell_click_condition) будем определять, был ли клик по ячейке таблицы:

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

```xml
<Condition Name="OperationCellClickCondition" Type="CellClickCondition" Assembly="Conditions">
  <Table Name="OperationDatabaseTable" />
</Condition>
```

{% endcode %}

Вторым условием будем проверять, что клик был по ячейке столбца State, который отвечает за сворачивание/разворачивание узлов. Для этого воспользуемся get-проперти [LastCellClickedColumnName](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable#get_last_cell_clicked_column_name) таблицы, чтобы получить имя колонки, по ячейке которой был последний клик:

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

```xml
<Condition Name="OperationLastCellClickedColumnNameEqualStateCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="OperationDatabaseTable">
        <Property Name="LastCellClickedColumnName" />
      </Object>
    </Item>
    <Item>State</Item>
  </Items>
</Condition>
```

{% endcode %}

Сворачивать/разворачивать мы можем только те узлы, у которых есть дочерние элементы. Здесь нам поможет get-проперти [SelectedRowCellValueByColumnName](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable#get_selected_row_cell_value_by_column_name) таблицы, которое по имени колонки будет возвращать значение ячейки в выделенной строке:

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

```xml
<Condition Name="SelectedOperationHasChildrenCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="OperationDatabaseTable">
        <Property Name="SelectedRowCellValueByColumnName">
          <Parameters>
            <Parameter Name="ColumnName">HasChildren</Parameter>
          </Parameters>
        </Property>
      </Object>
    </Item>
    <Item>True</Item>
  </Items>
</Condition>
```

{% endcode %}

Соберем все условия в одно условие типа [NestedCondition](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/conditions/nested_condition) и сделаем его принудительно событийным через тэг `<AlwaysChange>`:

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

```xml
<Condition Name="OperationToggleFoldingNodeNestedCondition" Type="NestedCondition" Assembly="Conditions">
  <AlwaysChange Value="True" />
  <ConditionExpression>
    <And>
      <Condition Name="OperationCellClickCondition" />
      <Condition Name="OperationLastCellClickedColumnNameEqualStateCondition" />
      <Condition Name="SelectedOperationHasChildrenCondition" />
    </And>
  </ConditionExpression>
</Condition>
```

{% endcode %}

Создайте на это условие Execution, оставив тэг `<Commands>` пустым.

Для сворачивания/разворачивания узла будем использовать set-проперти [ToggleFoldingNode](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc#set_toggle_folding_node) у TreeGetDataConnection, в параметр NodeId которого будем передавать идентификатор выбранной записи в таблице:

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

```xml
<Command Name="OperationToggleFoldingNodeValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <DataConnection Name="OperationTreeGetDataConnection">
    <Property Name="ToggleFoldingNode">
      <Parameters>
        <Parameter Name="NodeId">
          <Object Name="OperationDatabaseTable">
            <Property Name="SelectedRowCellValueByColumnName">
              <Parameters>
                <Parameter Name="ColumnName">OperationId</Parameter>
              </Parameters>
            </Property>
          </Object>
        </Parameter>
      </Parameters>
    </Property>
  </DataConnection>
</Command>
```

{% endcode %}

С помощью этой команды мы будем изменять данные в **OperationTreeGetDataConnection**. Однако это приведет к изменению источника данных в таблице, что послужит причиной для бесконечного цикла выполнения Execution на условие **OperationToggleFoldingNodeNestedCondition**. Поэтому мы создадим переменную, которую будем использовать для блокировки последовательности команд сворачивания/разворачивания:

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

```xml
<MyObject Name="BlockRefreshVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>True</Value>
</MyObject>
```

{% endcode %}

И команду для изменения значения этой переменной:

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

```xml
<Command Name="BlockRefreshVariableValueSetCommand" Type="ValueSetCommand" Assembly="Commands">
  <Object Name="BlockRefreshVariable">
    <Input />
  </Object>
</Command>
```

{% endcode %}

Создадим команду для обновления дерева OperationTreeGetDataConnection:

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

```xml
<Command Name="OperationTreeDataConnectionRefreshCommand" Type="DataConnectionRefreshCommand" Assembly="Commands">
  <DataConnections>
    <DataConnection Name="OperationTreeGetDataConnection" />
  </DataConnections>
</Command>
```

{% endcode %}

Теперь можем заполнить тэг `<Commands>` у Execution, в котором обрабатываем попытку свернуть/развернуть узел дерева:

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

```xml
<Execution>
  <ConditionExpression>
    <Condition Name="OperationToggleFoldingNodeNestedCondition" />
  </ConditionExpression>
  <Commands>
    <If>
      <When>
        <Object Name="BlockRefreshVariable" />
      </When>
      <Then>
        <Command Name="BlockRefreshVariableValueSetCommand">False</Command>
        <Command Name="OperationToggleFoldingNodeValueSetCommand" />
        <Command Name="OperationTreeDataConnectionRefreshCommand" />
        <Command Name="BlockRefreshVariableValueSetCommand">True</Command>
      </Then>
    </If>
  </Commands>
</Execution>
```

{% endcode %}

Запустите приложение и проверьте возможность сворачивать/разворачивать узлы дерева.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FrvrqIe3AhtgCgByjqve8%2Fimage.png?alt=media&amp;token=955548f6-ccf3-4624-8329-2d83c964fb8d" alt=""><figcaption></figcaption></figure>

### Удаление записи

Нельзя удалять служебные записи "ДОХОД" и "РАСХОД" и назначения платежей, которые находятся в архиве, содержат дочерние элементы или являются системными.

Добавим необходимые условия в тэг `<Enabled>` кнопки OperationDeleteButton:

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

```xml
<MyObject Name="OperationDeleteButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="OperationEditButton">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Object Name="OperationAddButton">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="OperationAddButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <Height>
    <Object Name="OperationAddButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <TabIndex>5</TabIndex>
  <Hint>Удалить запись</Hint>
  <BackgroundImage>Images\24x24\delete.png</BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <Enabled>
    <And>
      <Condition Name="OperationSelectedCondition" />
      <Not>
        <Condition Name="SelectedOperationIsArchiveCondition" />
      </Not>
      <Condition Name="SelectedOperationIdGreater0Condition" />
      <Condition Name="SelectedOperationIsNotSystemCondition" />
      <Not>
        <Condition Name="SelectedOperationHasChildrenCondition" />
      </Not>
    </And>
  </Enabled>
  <Commands>
    <Command Name="OperationDeleteMessageBoxCommand" />
  </Commands>
  <DisabledMode>True</DisabledMode>
  <DisabledText>
    <Switch>
      <Case>
        <When>
          <Not>
            <Condition Name="OperationSelectedCondition" />
          </Not>
        </When>
        <Then>Выберите запись для удаления.</Then>
      </Case>
      <Case>
        <When>
          <Condition Name="SelectedOperationIsArchiveCondition" />
        </When>
        <Then>Нельзя удалить архивную запись.</Then>
      </Case>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIdGreater0Condition" />
          </Not>
        </When>
        <Then>Выбранная запись является служебной и не может быть удалена.</Then>
      </Case>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIsNotSystemCondition" />
          </Not>
        </When>
        <Then>Выбранное назначение платежа является системным и не может быть удалено.</Then>
      </Case>
      <Case>
        <When>
          <Condition Name="SelectedOperationHasChildrenCondition" />
        </When>
        <Then>Выбранное назначение платежа содержит дочерние элементы и не может быть удалено.</Then>
      </Case>
    </Switch>
  </DisabledText>
</MyObject>
```

{% endcode %}

Так как теперь функция template.operation\_try\_delete(bigint) возвращает строку, то нам нужно переделать проверку результата команды удаления.

Создадим условия проверки результата выполнения команды **OperationDeleteSaveCommand**:

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

```xml
<Condition Name="OperationDeleteResultIsNullCondition" Type="IsNullCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Command Name="OperationDeleteSaveCommand" />
    </Item>
  </Items>
</Condition>

<Condition Name="OperationDeleteResultEqualParentCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Command Name="OperationDeleteSaveCommand" />
    </Item>
    <Item>parent</Item>
  </Items>
  <DataType Type="StringDataType" />
</Condition>

<Condition Name="OperationDeleteResultEqualUsedCondition" Type="EqualCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Command Name="OperationDeleteSaveCommand" />
    </Item>
    <Item>used</Item>
  </Items>
  <DataType Type="StringDataType" />
</Condition>
```

{% endcode %}

Создадим команду MessageBoxCommand вывода сообщения, что выбранное назначение содержит дочерние элементы:

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

```xml
<Command Name="OperationHasChildrenWarningMessageBoxCommand" Type="MessageBoxCommand" Assembly="Commands">
  <Caption>Удаление</Caption>
  <Text>Выбранное назначение платежа содержит дочерние элементы и не может быть удалено.</Text>
  <Icon Type="Information" />
  <Buttons Type="Ok" />
</Command>
```

{% endcode %}

Переделаем Execution, связанный с удалением записи:

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

```xml
<Execution>
  <ConditionExpression>
    <Command Name="OperationDeleteMessageBoxCommand" Parameter="Yes" />
  </ConditionExpression>
  <Commands>
    <Command Name="OperationDeleteSaveCommand" />
    <If>
      <When>
        <Condition Name="OperationDeleteResultIsNullCondition" />
      </When>
      <Then>
        <Command Name="UpdatedTrueValueSetCommand" />
      </Then>
      <ElseIf>
        <When>
          <Condition Name="OperationDeleteResultEqualParentCondition" />
        </When>
        <Then>
          <Command Name="OperationHasChildrenWarningMessageBoxCommand" />
        </Then>
      </ElseIf>
      <ElseIf>
        <When>
          <Condition Name="OperationDeleteResultEqualUsedCondition" />
        </When>
        <Then>
          <Command Name="OperationTryDeleteUsedMessageBoxCommand" />
          <Command Name="UpdatedTrueValueSetCommand" />
        </Then>
      </ElseIf>
    </If>
  </Commands>
</Execution>
```

{% endcode %}

Запустите приложение и проверьте удаление назначения платежа.

### Архивирование записи

Мы не можем отправлять в архив служебные записи "ДОХОД" и "РАСХОД" и системные назначения платежей.

Добавим необходимые условия в тэг `<Enabled>` кнопки OperationArchiveButton:

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

```xml
<MyObject Name="OperationArchiveButton" Type="Button" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="OperationDeleteButton">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Object Name="OperationAddButton">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="OperationAddButton">
      <Property Name="Width" />
    </Object>
  </Width>
  <Height>
    <Object Name="OperationAddButton">
      <Property Name="Height" />
    </Object>
  </Height>
  <TabIndex>6</TabIndex>
  <Hint>
    <Switch>
      <Case>
        <When>
          <Condition Name="SelectedOperationIsArchiveCondition" />
        </When>
        <Then>Восстановить запись из архива</Then>
      </Case>
      <Case>Перенести запись в архив</Case>
    </Switch>
  </Hint>
  <BackgroundImage>
    <Switch>
      <Case>
        <When>
          <Or>
            <Condition Name="SelectedOperationIsArchiveCondition" />
            <Object Name="ArchiveFilterComboBox" />
          </Or>
        </When>
        <Then>Images\24x24\unarchive.png</Then>
      </Case>
      <Case>Images\24x24\archive.png</Case>
    </Switch>
  </BackgroundImage>
  <BackgroundImageLayout>Center</BackgroundImageLayout>
  <FlatStyle>Flat</FlatStyle>
  <FlatBorderSize>1</FlatBorderSize>
  <FlatBorderColor>ButtonFlatBorderColor</FlatBorderColor>
  <FlatMouseDownBackColor>ButtonFlatMouseDownBackColor</FlatMouseDownBackColor>
  <FlatMouseOverBackColor>ButtonFlatMouseOverBackColor</FlatMouseOverBackColor>
  <Enabled>
    <And>
      <Condition Name="OperationSelectedCondition" />
      <Condition Name="SelectedOperationIdGreater0Condition" />
      <Condition Name="SelectedOperationIsNotSystemCondition" />
    </And>
  </Enabled>
  <Commands>
    <Command Name="OperationArchiveMessageBoxCommand" />
  </Commands>
  <DisabledMode>True</DisabledMode>
  <DisabledText>
    <Switch>
      <Case>
        <When>
          <Not>
            <Condition Name="OperationSelectedCondition" />
          </Not>
        </When>
        <Then>
          <Switch>
            <Case>
              <When>
                <Object Name="ArchiveFilterComboBox" />
              </When>
              <Then>Для восстановления из архива выберите запись.</Then>
            </Case>
            <Case>Для переноса в архив выберите запись.</Case>
          </Switch>
        </Then>
      </Case>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIdGreater0Condition" />
          </Not>
        </When>
        <Then>Выбранная запись является служебной и не может быть перенесена в архив.</Then>
      </Case>
      <Case>
        <When>
          <Not>
            <Condition Name="SelectedOperationIsNotSystemCondition" />
          </Not>
        </When>
        <Then>Выбранное назначение платежа является системным и не может быть перенесено в архив.</Then>
      </Case>
    </Switch>
  </DisabledText>
</MyObject>
```

{% endcode %}

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

В OperationArchiveSetDataConnection добавьте параметр Archive. Передавайте в него значение, противоположное значению из ячейки колонки Archive в выделенной строке таблицы. Для этого используйте тэг `<Not>`.

Переделайте текст запроса OperationArchiveUpdateSqlQuery:

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

```xml
<SqlQuery Name="OperationArchiveUpdateSqlQuery">
  <Text>
    SELECT template.operation_try_archive({OperationId}::bigint, {Archive}::boolean);
  </Text>
</SqlQuery>
```

{% endcode %}

<details>

<summary><strong>template.operation_try_archive(bigint, boolean)</strong></summary>

```sql
CREATE OR REPLACE FUNCTION template.operation_try_archive(
    in_operation_id bigint,
    in_archive boolean)
  RETURNS character varying AS
$BODY$
DECLARE
  _is_system boolean;
BEGIN
  WITH RECURSIVE tree (operation_id, id_title) AS (
    SELECT
      operation_id,
      id_title
    FROM template.operation
    WHERE operation_id = in_operation_id

    UNION

    SELECT
      O.operation_id,
      O.id_title
    FROM 
      template.operation O
      JOIN tree T ON O.operation_category_id = T.operation_id 
  )
  SELECT bool_or(id_title IS NOT NULL) FROM tree INTO _is_system;

  IF (_is_system) THEN
    RETURN 'system';
  END IF;

  UPDATE template.operation
  SET archive = in_archive
  WHERE operation_id = in_operation_id;

  PERFORM template.operation_archive_child(in_operation_id) WHERE in_archive;
  PERFORM template.operation_unarchive_parent(in_operation_id) WHERE NOT in_archive;

  RETURN NULL;
END;
$BODY$
  LANGUAGE plpgsql;
```

Функция архивирования дочерних элементов:

```sql
CREATE OR REPLACE FUNCTION template.operation_archive_child(in_operation_id bigint)
  RETURNS void AS
$BODY$
DECLARE
  _archive boolean;
  _operation_id bigint;
BEGIN
  SELECT archive INTO _archive FROM template.operation WHERE operation_id = in_operation_id;
  IF (NOT _archive) THEN RETURN; END IF;

  FOR _operation_id IN (SELECT operation_id FROM template.operation WHERE operation_category_id = in_operation_id)
  LOOP
    UPDATE template.operation
    SET archive = _archive
    WHERE operation_id = _operation_id;

    PERFORM template.operation_archive_child(_operation_id);
  END LOOP;
END;
$BODY$
  LANGUAGE plpgsql;
```

Функция разархивирования родительской записи:

```sql
CREATE OR REPLACE FUNCTION template.operation_unarchive_parent(in_operation_id bigint)
  RETURNS void AS
$BODY$
DECLARE
  _operation_category_id bigint;
  _archive boolean;
BEGIN
  SELECT operation_category_id, archive INTO _operation_category_id, _archive
  FROM template.operation WHERE operation_id = in_operation_id;
  
  IF (_archive) THEN RETURN; END IF;

  IF (_operation_category_id IS NOT NULL) THEN
    UPDATE template.operation
    SET archive = _archive
    WHERE operation_id = _operation_category_id;

    PERFORM template.operation_unarchive_parent(_operation_category_id);
  END IF;
END;
$BODY$
  LANGUAGE plpgsql;
```

</details>

Запустите приложение и проверьте работу с архивом.

## Список счетов

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

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FpMdCoZLU53rKjzpItaSd%2F04.png?alt=media&amp;token=ad0f0f23-9c9c-4963-9f25-26556484d712" alt=""><figcaption></figcaption></figure>

### База данных

Выполним скрипт на создание таблицы в базе данных:

```sql
CREATE SEQUENCE template.account_id_seq;
CREATE TABLE template.account
(
  account_id smallint NOT NULL DEFAULT nextval('template.account_id_seq'::regclass),
  title character varying NOT NULL,
  cash boolean NOT NULL,
  archive boolean NOT NULL DEFAULT false,
  CONSTRAINT pk_account_id PRIMARY KEY (account_id)
);
```

В поле **cash** будем задавать тип счета: наличный (true) или безналичный (false).

Ранее в уроке создали таблицу template.cash, в которой указали поле account\_id, Теперь можем на это поле создать внешний ключ, который будет ссылаться на новую таблицу:

```sql
ALTER TABLE template.cash
  ADD CONSTRAINT fk_account_id FOREIGN KEY (account_id)
  REFERENCES template.account (account_id) ON UPDATE NO ACTION ON DELETE NO ACTION;
```

### Форма списка

На главной форме (TemplateStart.xml) в меню добавим пункт **Списки -> Счета...**, по которому будет открываться новая форма (TemplateAccountList.xml).

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FkrMHQZTQzBqopeNkRyPS%2F03.png?alt=media&amp;token=8dcbe366-182f-4bed-bb4a-31c082af6f6a" alt=""><figcaption></figcaption></figure>

Создадим необходимые формы для списка счетов с помощью шаблона ArchiveList:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FHtjWUVbrcOINrI8odOEP%2Fimage.png?alt=media&amp;token=80ead272-5a00-4ca8-975c-6a739b99bd04" alt=""><figcaption></figcaption></figure>

Самостоятельно скорректируйте SQL-запрос для получения списка счетов и связанный с ним PrimaryGetDataConnection, добавив колонку **AccountType**, в которую должны попадать данный из поля **cash** таблицы **template.account**.

На форме списка счетов (TemplateAccountList.xml) в таблицу добавьте колонки:

```xml
<Column Name="AccountType" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Visible>False</Visible>
</Column>
<Column Name="AccountTypeText" Type="DatabaseTableColumnTextBox" Assembly="DatabaseTableColumnControls">
  <Title>Тип счета</Title>
  <Width>100</Width>
  <AutoSizeMode Value="None" />
  <Substitution SourceColumn="AccountType">
    <Structure Type="Table">
      <Row>
        <Item>True</Item>
        <Item>Наличный</Item>
      </Row>
      <Row>
        <Item>False</Item>
        <Item>Безналичный</Item>
      </Row>
    </Structure>
  </Substitution>
</Column>
```

Обратите внимание, что в качестве таблицы для подстановки значения в тэге `<Substitution>` используем структуру [`<Structure Type="Table">`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/values/structure#table).

Не забудьте создать функцию *template.account\_try\_delete(smallint)*.

### Карточка сущности

Перейдем в файл карточки счета (TemplateAccountEdit.xml) и добавим поле "Тип счета":

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

```xml
<MyObject Name="AccountTypeLabel" Type="Label" Assembly="BaseControls">
  <Top>
    <Calculate>
      <Expression>{0} + 5</Expression>
      <Items>
        <Item>
          <Object Name="TitleTextBox">
            <Property Name="Bottom" />
          </Object>
        </Item>
      </Items>
    </Calculate>
  </Top>
  <Left>
    <Object Name="TitleLabel">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="TitleLabel">
      <Property Name="Width" />
    </Object>
  </Width>
  <Height>20</Height>
  <TextAlign>MiddleLeft</TextAlign>
  <FontStyle>LabelFontStyle</FontStyle>
  <ForeColor>LabelForeColor</ForeColor>
  <Text>Тип счета</Text>
</MyObject>

<MyObject Name="AccountTypeComboBox" Type="ComboBox" Assembly="BaseControls">
  <Top>
    <Object Name="AccountTypeLabel">
      <Property Name="Bottom" />
    </Object>
  </Top>
  <Left>
    <Object Name="TitleTextBox">
      <Property Name="Left" />
    </Object>
  </Left>
  <Width>
    <Object Name="TitleTextBox">
      <Property Name="Width" />
    </Object>
  </Width>
  <TabIndex>2</TabIndex>
  <NullValue Show="True" Title="[Не выбрано]" />
  <AutoCompleteMode>SmartSuggest</AutoCompleteMode>
  <ValueList>
    <Structure Type="Table">
      <Row>
        <Item>True</Item>
        <Item>Наличный</Item>
      </Row>
      <Row>
        <Item>False</Item>
        <Item>Безналичный</Item>
      </Row>
    </Structure>
  </ValueList>
  <Value>
    <DataConnection SourceDataConnection="AccountPrimaryGetDataConnection">
      <Fields>
        <Field Name="AccountType" />
      </Fields>
    </DataConnection>
  </Value>
</MyObject>
```

{% endcode %}

Для задания значений выпадающего списка так же используем структуру `<Structure Type="Table">`.

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2F9yiuI1DVpBzvizn6gbwK%2Fimage.png?alt=media&amp;token=e6e34ea3-cea2-43ee-9000-913d5b4be8c3" alt=""><figcaption></figcaption></figure>

Отлично! Теперь можем перейти к заказам и добавить в них таблицу оплат.

## Оплаты в заказе

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

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FEU8L535kfz0PQX4MQbMe%2F05.png?alt=media&amp;token=4857d968-afb5-4ea9-a33e-0409cc82016c" alt=""><figcaption></figcaption></figure>

### База данных

Выполним скрипт на создание таблицы в базе данных:

```sql
CREATE SEQUENCE template.order_payment_id_seq;
CREATE TABLE template.order_payment
(
  order_payment_id bigint NOT NULL DEFAULT nextval('template.order_payment_id_seq'::regclass),
  order_id bigint NOT NULL,
  cash_id bigint NOT NULL,
  CONSTRAINT pk_order_payment_id PRIMARY KEY (order_payment_id),
  CONSTRAINT fk_cash_id FOREIGN KEY (cash_id)
      REFERENCES template.cash (cash_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
);
```

Дату, счет и сумму оплаты мы будем хранить в таблице **template.cash**, которую создали ранее в уроке, а в таблице **template.order\_payment** будем хранить привязку кассовой операции к заказу через их идентификаторы.

В серверном файле (Template.xml) переделаем запрос на получение списка оплат в заказе:

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

```xml
<SqlQuery Name="OrderPaymentSelectSqlQuery">
  <Text>  
    SELECT
      OP.order_payment_id AS "OrderPaymentId",
      C.cash_id AS "CashId",
      C.cash_date AS "Date",
      C.account_id AS "AccountId",
      C.summ AS "Summ"
    FROM
      template.order_payment OP
      JOIN template.cash C USING(cash_id)
    WHERE
      NOT C.deleted AND
      OP.order_id = {OrderId}
    ORDER BY C.cash_date, OP.order_payment_id;
  </Text>
</SqlQuery>
```

{% endcode %}

Так же скорректируем запрос на удаление оплаты:

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

```xml
<SqlQuery Name="OrderPaymentDeleteSqlQuery">
  <Text>
    UPDATE template.cash
    SET
      deleted = TRUE,
      date_deleted = NOW()
    FROM
      template.order_payment OP
    WHERE
      cash.cash_id = OP.cash_id AND
      OP.order_payment_id = {OrderPaymentId};
  </Text>
</SqlQuery>
```

{% endcode %}

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

Перейдем в файл карточки заказа (TemplateOrderEdit.xml). Под панелью **OrderPositionPanel** создадим панель **OrderPaymentPanel** высотой 200.

#### Таблица оплат

С помощью паттерна **Table** создадим таблицу оплат, поместив курсор в описание панели OrderPaymentPanel.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fa92whhWs2hGflQ7JnYcd%2Fimage.png?alt=media&amp;token=1ecb3f29-9ced-4223-8c17-c99871f62fba" alt=""><figcaption></figcaption></figure>

Скорректируйте таблицу OrderPaymentDatabaseTable, добавив необходимые колонки. Для колонки Счет (AccountTitle) используйте тэг [`<Substitution>`](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/objects/databasetable/column#column_substitution) по колонке AccountId. Для этого создайте AccountPrimaryGetDataConnection. Так же добавьте атрибут `ChangeForm="False"` для таблицы оплат, чтобы изменение ее источника данных не приводило к изменению формы и активации кнопки "Сохранить" - сохранение оплаты уже было на дочерней форме.

Не забудьте добавить соответствующие поля в OrderPaymentPrimaryGetDataConnection.

#### Итоговые значения

На панель **TotalPanel** добавьте поля "Сумма оплат" (TotalPaidSumTextBox) и "Задолженность" (TotalDebtTextBox).

Для **TotalPaidSumTextBox** создадим переменную TotalPaidSumVariable:

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

```xml
<MyObject Name="TotalPaidSumVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Object Name="OrderPaymentDatabaseTable">
      <Property Name="ColumnSum">
        <Parameters>
          <Parameter Name="ColumnName">Summ</Parameter>
        </Parameters>
      </Property>
    </Object>
  </Value>
</MyObject>
```

{% endcode %}

А для объекта **TotalDebtTextBox** будем использовать переменную TotalDebtVariable:

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

```xml
<MyObject Name="TotalDebtVariable" Type="Variable" Assembly="SimpleControls" ChangeForm="False">
  <Value>
    <Calculate>
      <Expression>{0} - {1}</Expression>
      <Items>
        <Item>
          <Object Name="TotalSumToPayVariable" />
        </Item>
        <Item>
          <Object Name="TotalPaidSumVariable" />
        </Item>
      </Items>
    </Calculate>
  </Value>
</MyObject>
```

{% endcode %}

### Карточка оплаты

Создайте в файле карточки оплаты (TemplateOrderPaymentEdit.xml) все необходимые элементы, чтобы у вас получилась форма вида:

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2Fk4yXi7OeYSuwWzOPFDV1%2Fimage.png?alt=media&amp;token=2e592b92-1f73-4f99-8eca-7dc54955111f" alt=""><figcaption></figcaption></figure>

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

Переделаем запросы:

<details>

<summary>OrderPaymentByIdSelectSqlQuery</summary>

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

```xml
<SqlQuery Name="OrderPaymentByIdSelectSqlQuery">
  <Text>
    SELECT
      C.cash_date AS "Date",
      C.account_id AS "AccountId",
      C.summ AS "Summ"
    FROM
      template.order_payment OP
      JOIN template.cash C USING(cash_id)
    WHERE
      OP.order_payment_id = {OrderPaymentId};
  </Text>
</SqlQuery>
```

{% endcode %}

</details>

<details>

<summary>OrderPaymentInsertSqlQuery</summary>

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

```xml
<SqlQuery Name="OrderPaymentInsertSqlQuery">
  <Text>
    INSERT INTO template.cash(
      cash_date,
      account_id,
      operation_id,
      summ
    )
    SELECT
      {Date},
      {AccountId},
      O.operation_id,
      {Summ}
    FROM
      template.operation O
    WHERE
      id_title = 'OrderPaymentOperation';

    INSERT INTO template.order_payment(
      order_id,
      cash_id
    )
    SELECT
      {OrderId},
      currval('template.cash_id_seq'::regclass)
    RETURNING order_payment_id;
  </Text>
</SqlQuery>
```

{% endcode %}

Функция `currval` позволяет получить текущее значение последовательности, а в данном случае идентификатор только что добавленной записи.

В статье [Функции nextval и currval](https://wfsys.gitbook.io/wt-knowledge-base/sql/function_nextval_currval) в Базе знаний дана информация о способах получения значения последовательности.

</details>

<details>

<summary>OrderPaymentUpdateSqlQuery</summary>

<pre class="language-xml" data-title="Template.xml"><code class="lang-xml">&#x3C;SqlQuery Name="OrderPaymentUpdateSqlQuery">
  &#x3C;Text>
    UPDATE template.cash
<strong>    SET
</strong>      cash_date = {Date},
      account_id = {AccountId},
      summ = {Summ}
    FROM
      template.order_payment OP
    WHERE
      cash.cash_id = OP.cash_id AND
      OP.order_payment_id = {OrderPaymentId};
  &#x3C;/Text>
&#x3C;/SqlQuery>
</code></pre>

</details>

Отлично! Запустите приложение и проверьте загрузку форм. Добавьте несколько оплат в заказ.

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FBW1TFI1neRaHHfwx6T2z%2Fimage.png?alt=media&amp;token=b865f099-203c-4af0-8f55-72bce42d44a9" alt=""><figcaption></figcaption></figure>

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

### Список заказов

На главной форме (TemplateStart) в таблицу **OrderDatabaseTable** добавьте колонки: Сумма заказа (TotalOrderCost), Сумма оплат (TotalPaymentSumm) и Задолженность (TotalDebt):

<figure><img src="https://3019442075-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M_eBlWEU4C3o2GVEAAr%2Fuploads%2FA4ze4jc35qxv1eLrAbAs%2Fimage.png?alt=media&amp;token=f6a289c1-2f11-43c5-bbd9-a1c4d7ab286b" alt=""><figcaption></figcaption></figure>

Скорректируйте текст запроса OrderSelectSqlQuery.

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

В этом уроке мы познакомились с [TreeGetDataConnection](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/dataconnections/tree_get_dc) и возможностью отображать дерево записей в таблице DatabaseTable. На следующем уроке вы самостоятельно реализуете модуль кассы и отчет по бюджету с выгрузкой его в документ Excel с помощью команды [ExportToExcelCommand](https://wfsys.gitbook.io/workflow-forms-syntax/workflow_forms/commands/export_to_excel_command).

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