WT. Практика (Desktop)
Платформа WTПрактикаСинтаксисБаза знаний
  • Приветствие
  • Основной
    • Урок 1. Форма списка и добавление записей
    • Урок 2. Редактирование таблицы
    • Урок 3. Выпадающий список
    • Урок 4. Паттерн onClose
    • Урок 5. Удаление связанных данных
    • Урок 6. Главная форма
    • Урок 7. Фильтры
    • Урок 8. Редактирование выпадающего списка
    • Урок 9. Список категорий
    • Урок 10. Паттерн Add/Edit
    • Урок 11. Экспорт данных в документ
    • Урок 12. Дерево в таблице
    • Урок 13. Самостоятельная
    • Урок 14. Постраничный просмотр
    • Дополнительно
      • Array
      • ArrayGetDataConnection
      • ConvertDataConnection
  • Загрузка данных
    • Урок 15. Режимы загрузки данных
    • Урок 16. Режим блокировки форм (Lock)
  • Многопользовательский режим
    • Урок 17. Аутентификация пользователей в программе
    • Урок 18. Права доступа
    • Урок 19. Динамические права доступа
    • Урок 20. Пользовательские настройки
    • Урок 21. Автоматическое обновление данных
  • Кастомизация
    • Урок 22. Создание кастомных команд для форм
    • Урок 23. Создание кастомных команд для серверной части
    • Урок 24. Планировщик задач
  • Продвинутый уровень
    • Урок 25. Создание API-запросов
    • Урок 26. Работа с JSON на форме
    • Урок 27. Разделение формы на несколько файлов
Powered by GitBook
On this page
  • Касса
  • Форма кассы
  • Карточка кассовой операции
  • Отчет по бюджету
  • Основной отчет
  • Детализация отчета
  • Ответы
  1. Основной

Урок 13. Самостоятельная

Last updated 1 year ago

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

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

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

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

Касса

Форма кассы должна открываться с главной формы через пункт меню Финансы -> Касса.

Форма кассы

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

В запросе для кассы для параметров {DateStart} и {DateFinish} используйте функцию convert_date_filter(), которая приводит дату фильтра к дате со временем в часовой зоне сервера.

Таблица операций

Таблица кассовых операций должна содержать колонки:

  • RowNumber (№) - порядковый номер строки;

  • CashId - идентификатор кассовой операции из таблицы template.cash;

  • OrderId - идентификатор заказа из таблицы template.order. Актуально только для операций "Оплата заказа";

  • Date (Дата операции) - дата в формате "dd.MM.yyyy HH:mm";

  • AccountTitle (Счет) - наименование счета;

  • OperationTitle (Назначение платежа) - наименование назначения платежа;

  • Income (Приход) - сумма кассовой операции или оплаты заказа, если тип назначение платежа "Доход";

  • Expense (Расход) - сумма кассовой операции, если тип назначение платежа "Расход";

  • ClientTitle (Клиент) - имя клиента.

Редактирование таблицы кассовых операций

Операции системного назначения "Оплата заказа" можно создавать, редактировать и удалять только из карточки заказа.

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

Остальные операции редактируются в карточке кассовой операции.

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

Удаление кассовой операции делать через поля deleted и date_deleted - это позволит не терять данные.

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

Результирующие поля

Под таблицей создайте результирующие поля (TextBox), которые будут привязаны к столбцам Income (Приход) и Expense (Расход).

Текст необходимо выровнять по правому краю.

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

Пример тэга <Text> для результирующего поля
<Text>
  <String>
	<Format>{0} /
{1}</Format>
	<Items>
	  <Item>
		<DataTypeFormat Type="DecimalDataType" Format="N2">
		  <Object Name="CashDatabaseTable">
			<Property Name="SelectedCellsSumByColumnName">
			  <Parameters>
				<Parameter Name="ColumnName">Income</Parameter>
			  </Parameters>
			</Property>
		  </Object>
		</DataTypeFormat>
	  </Item>
	  <Item>
		<DataTypeFormat Type="DecimalDataType" Format="N2">
		  <Object Name="CashDatabaseTable">
			<Property Name="ColumnSum">
			  <Parameters>
				<Parameter Name="ColumnName">Income</Parameter>
			  </Parameters>
			</Property>
		  </Object>
		</DataTypeFormat>
	  </Item>
	</Items>
  </String>
</Text>

Выгрузка данных в Excel

Создайте xlsx-файл шаблона для экспорта в Excel кассы за выбранный период. При выгрузке данных у вас должен получаться документ вида:

Карточка кассовой операции

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

Для поля "Назначение платежа" (OperationComboBox) используйте SQL-запрос:

Template.xml
<SqlQuery Name="OperationCashSelectSqlQuery">
  <Text>
    SELECT
      operation_id AS "OperationId",
      title AS "Title",
      archive AS "Archive"
    FROM template.operation O
    WHERE
      id_title IS NULL AND
      NOT EXISTS (SELECT * FROM template.operation O2 WHERE O2.operation_category_id = O.operation_id)
    ORDER BY title, operation_id ASC;
  </Text>
</SqlQuery>

Запрос возвращает назначения платежей, которые не являются системными и не имеют дочерних назначений.

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

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

Отчет по бюджету

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

Основной отчет

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

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

BudgetReportSelectSqlQuery
Template.xml
<SqlQuery Name="BudgetReportSelectSqlQuery">
  <Text>
    WITH RECURSIVE _operation_tree (operation_id, operation_category_id, title, income, archive, path, child_array) AS (
      SELECT -2::integer, NULL::bigint, 'ДОХОД', True, False, ARRAY[-2]::integer[], template.operation_get_child(null, true)

      UNION

      SELECT -1::integer, NULL::bigint, 'РАСХОД', False, False, ARRAY[-1]::integer[], template.operation_get_child(null, false)

      UNION

      SELECT
        O.operation_id,
        T.operation_id,
        O.title,
        O.income,
        O.archive,
        T.path || O.operation_id,
        template.operation_get_child(O.operation_id)
      FROM
        template.operation O, _operation_tree T
      WHERE
        COALESCE(O.operation_category_id, CASE WHEN O.income THEN -2 ELSE -1 END) = T.operation_id
    ), _cash AS (
      SELECT
        Oper.operation_id,
        Oper.income,
        SUM(summ) AS summ
      FROM
        template.cash C
        JOIN template.operation Oper USING(operation_id)
        LEFT JOIN template.order_payment OP USING(cash_id)
        LEFT JOIN template.order O USING(order_id)
      WHERE
        NOT C.deleted AND
        NOT COALESCE(O.deleted, false) AND
        C.cash_date BETWEEN convert_date_filter({DateStart}::timestamp) AND convert_date_filter({DateFinish}::timestamp) + interval '1d - 1s'
      GROUP BY Oper.operation_id
    ), report_tmp AS (
      SELECT
        O.operation_id,
        O.operation_category_id,
        O.title AS operation_title,
        O.income,
        O.archive,
        O.path,
        COALESCE(C.summ, 0) summ
      FROM
        _operation_tree O
        LEFT JOIN LATERAL
        (
          SELECT SUM(summ) AS summ
          FROM _cash C
          WHERE C.operation_id = ANY(O.child_array)
        ) AS C ON True
    )
    SELECT
      true AS "Expand",
      operation_id AS "OperationId",
      operation_category_id AS "OperationCategoryId",
      operation_title AS "OperationTitle",
      summ AS "Summ",
      operation_id IN (-1, -2) AS "Total",
      income AS "Income",
      false AS "Profit"
    FROM report_tmp
    WHERE
      NOT archive OR
      summ != 0

    UNION ALL

    SELECT
      true AS "Expand",
      -3 AS "OperationId",
      NULL AS "OperationCategoryId",
      'Прибыль' AS "OperationTitle",
      SUM(summ * CASE WHEN income THEN 1 ELSE -1 END) AS "Summ",
      false AS "Total",
      NULL AS "Income",
      true AS "Profit"
    FROM report_tmp
    WHERE operation_id IN (-1, -2);
  </Text>
</SqlQuery>

Обратите внимание, что для параметров {DateStart} и {DateFinish} используется функция convert_date_filter(), которая приводит дату фильтра к дате со временем в часовой зоне сервера.

В запросе используется функция template.operation_get_child(), которая возвращает массив идентификаторов всех вложенных дочерних элементов выбранного назначения платежа:

CREATE OR REPLACE FUNCTION template.operation_get_child(
    in_operation_id bigint,
    in_income boolean DEFAULT true)
  RETURNS bigint[] AS
$BODY$
DECLARE
  _child_operation_id_array bigint[];
  _child_operation_id bigint;
  _child_operation_record record;
  _result bigint[];
BEGIN
  IF (in_operation_id IS NOT NULL)
  THEN
    _child_operation_id_array = (SELECT array_agg(operation_id ORDER BY operation_id) FROM template.operation WHERE operation_category_id = in_operation_id);

    IF (_child_operation_id_array IS NULL)
    THEN
      _result = ARRAY[in_operation_id];
    ELSE
      FOREACH _child_operation_id IN ARRAY _child_operation_id_array
        LOOP
          _result = _result || (SELECT template.operation_get_child(_child_operation_id));
        END LOOP;
    END IF;
  ELSE
    FOR _child_operation_record IN (SELECT operation_id FROM template.operation WHERE (income = in_income OR in_income IS NULL) AND operation_category_id IS NULL ORDER BY operation_id)
    LOOP
      _result = _result || (SELECT template.operation_get_child(_child_operation_record.operation_id));
    END LOOP;
  END IF;

  RETURN _result;
END;
$BODY$
  LANGUAGE plpgsql;

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

Для каждого отчета все необходимые запросы лучше группировать в отдельный SqlQueryPermission, для которого создавать отдельную Role. Это позволит в будущем гибче настраивать права доступа к отдельным отчетам.

Создайте xlsx-файл шаблона для экспорта в Excel отчета за выбранный период. При выгрузке данных у вас должен получаться документ вида:

Детализация отчета

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

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

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

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

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

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

BudgetReportDetailSelectSqlQuery
Template.xml
<SqlQuery Name="BudgetReportDetailSelectSqlQuery">
  <Text>
    WITH operation_tmp AS (
      SELECT template.operation_get_child({OperationId}, {Income}) AS operation_id_array
    )
    SELECT
      C.cash_id AS "CashId",
      O.order_id AS "OrderId",
      C.cash_date AS "Date",
      A.title AS "AccountTitle",
      Oper.title AS "OperationTitle",
      C.summ * (CASE WHEN NOT Oper.income AND {OperationId} IS NULL AND {Income} IS NULL THEN -1 ELSE 1 END)AS "Summ",
      Cl.title AS "ClientTitle"
    FROM
      operation_tmp, template.cash C
      JOIN template.account A USING(account_id)
      JOIN template.operation Oper USING(operation_id)
      LEFT JOIN template.order_payment OP USING(cash_id)
      LEFT JOIN template.order O USING(order_id)
      LEFT JOIN template.client Cl USING(client_id)
    WHERE
      NOT C.deleted AND
      NOT COALESCE(O.deleted, false) AND
      C.cash_date BETWEEN convert_date_filter({DateStart}::timestamp) AND convert_date_filter({DateFinish}::timestamp) + interval '1d - 1s' AND
      C.operation_id = ANY(operation_id_array)
    ORDER BY C.cash_date, C.cash_id;
  </Text>
</SqlQuery>

Обратите внимание, что для параметров {DateStart} и {DateFinish} используется функция convert_date_filter(), которая приводит дату фильтра к дате со временем в часовой зоне сервера.

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

Создайте xlsx-файл шаблона для экспорта в Excel детализации отчета за выбранный период. При выгрузке данных у вас должен получаться документ вида:

Отлично! Теперь у нас в проекте есть финансовый блок.

Ответы

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

Для иконки используйте тэг , а для выравнивания - .

Для задания левой координаты объекта TextBox используйте get-проперти , которое вернет левую координату столбца таблицы относительно контейнера, в котором располагается таблица.

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

Для задания ширины объекта TextBox используйте get-проперти , которое вернет ширину определенного столбца.

Высоту объектов TextBox задайте равной 35 и переведите их в многострочный режим, указав тэг со значением True. Если это не сделать, высота объекта TextBox останется стандартной.

Для таблицы задайте свойство со значением True для атрибута Value. Это позволит пользователю выделять несколько строк.

Если пользователи могут менять видимость колонок в таблице DatabaseTable (данная возможность будет рассматриваться в уроке ), то видимость результирующих полей следует привязать к видимости соответствующих колонок через get-проперти .

Для кнопки "Экспорт в Excel..." используйте картинку из архива и команду типа .

В шаблоне должно быть три блока: "ДОХОД", "РАСХОД" и "Прибыль". Для получения нужных строк из таблицы DatabaseTable используйте get-проперти .

<Image>
<ImageAlign>
ColumnLeft
ColumnOffset
ColumnWidth
<Multiline>
MultiSelect
ExportToExcelCommand
FilteredColumnValues
Разворачивание проекта
ColumnVisible
ссылке
Ответы
Пользовательские настройки
921B
lesson13-Images.zip
archive
Иконка для кнопки
509KB
lesson13-answer.zip
archive