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

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

Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.

При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.

Инструкция по подключению шаблонов находится по ссылке.

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

Форма заказа

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

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

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 - панель для таблицы с позициями в заказе.

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

  • ClientPanel - сюда перенесите поля связанные с клиентом;

  • OrderPanel - сюда перенесите поля номер и дату заказа и комментарий к заказу;

  • TotalPanel - Здесь создайте текстовое поле "Всего к оплате" (TotalSumToPayTextBox).

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

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

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

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

<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> со значением атрибута Value равным 50.

Создадим переменную Variable, в которой будем считать сумму значений во всех строках колонки TotalPrice таблицы OrderPositionDatabaseTable:

TemplateOrderEdit.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>

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

TemplateOrderEdit.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>

Здесь используем универсальное значение DataTypeFormat для задания формата строки, в которую будет преобразовано значение переменной.

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

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

Template.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>

Из запроса мы убрали поле Title, так как одноименную колонку в таблице OrderPositionDatabaseTable будем заполнять с помощью тэга <Substitution> по колонке MaterialId. Для этого подготовим запрос для получения списка ТМЦ:

Template.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>

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

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

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

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

TemplateOrderEdit.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>

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

TemplateOrderEdit.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>

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

<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 сделаем модальными.

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

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

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

Template.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>

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

TemplateOrderPositionEdit.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>

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

Петтерн редактируемого выпадающего списка

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

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

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

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

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

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

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

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

Template.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>

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

TemplateOrderPositionEdit.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>

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

TemplateOrderPositionEdit.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>

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

TemplateOrderPositionEdit.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>

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

  • Поле "Количество" (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. Только для чтения.

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

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

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

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

Первым делом создадим условие ChangedCondition, которое отслеживает изменение значения выпадающего списка:

TemplateOrderPositionEdit.xml
<Condition Name="MaterialComboBoxChangedCondition" Type="ChangedCondition" Assembly="Conditions">
  <Items>
    <Item>
      <Object Name="MaterialComboBox" />
    </Item>
  </Items>
</Condition>

Добавим в описание MaterialComboBox вложенный тэг <Change> со значениями:

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

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

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

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

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

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

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

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

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

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

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

Проблема

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

Как видно из сообщения об ошибке, значением параметра OrderId является NULL, а это противоречит ограничению NOT NULL в описании колонки order_id в таблице template.order_position. Иными словами, мы не можем сохранить позицию заказа в базе данных, пока не сохраним сам заказ и не получим его идентификатор.

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

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

Суть решения заключается в том, что мы передаем данные на родительскую форму, пишем их в таблицу DatabaseTable через ее свойства AddRow, AddRows, UpdateRow, UpdateRows и DeleteRowsByIndices, как мы делали на форме списка городов. При попытке сохранить новый заказ выполняем последовательность из двух команд типа SaveCommand:

  • первая для SetDataConnection на вставку самой записи заказа, с возвратом на форму идентификатора нового заказа в результат команды SaveCommand;

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

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

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

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

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

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

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

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

Паттерн Add/Edit

Подготовка

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

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:

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:

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

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

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

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

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

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

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

TemplateStart.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>

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

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

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

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

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

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

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

Template.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>

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

TemplateOrderEdit.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>

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

Итоги

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

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

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

Ответы

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

Last updated