Как в ЦФТ реализовать то, что мы всегда делали в Бисквите (с примерами)

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

Привет, Хабр!!!

Редакторы советуют начинать статью именно с такого приветствия, ну так я спорить не буду:) Меня зовут Баранов Михаил, работаю программистом более 20 лет. В далеком, примерно 2003-м году умные люди мне сказали: «Бросай этот язык программирования Progress 4Gl, он устарел, срочно переходи на какой-нибудь другой». Я не согласился. «На этом языке написано несколько банковских систем, и я буду востребован», - подумал я. Тогда я устроился работать с банковской системой Банкир/Про написанной на языке Progress. Работал с ней около 5 лет. Но потом банки стали от нее отказываться. Тогда я стал работать с системой Бисквит тоже написанной на языке Progress. Прошло еще 10 лет и от Бисквита стали банки отказываться. И вот в 2019 году я оказался в крайне неприятной ситуации: я отлично знаю только то, что уже мало кому нужно. Осенью 2019 года я получил отличное предложение от РСХБ-ИНТЕХ, где я сейчас и работаю. Я устроился туда выполнять задачи по разработке в системе Бисквит и одновременно изучать систему ЦФТ на языке программирования Pl Plus. Уже на испытательном сроке мне назначили 14 учебных курсов на учебном портале ЦФТ и стали давать элементарные задачки по разработке в системе ЦФТ. А примерно с лета 2020 года я полностью перешел на разработку в системе ЦФТ и этому очень рад.

Эта статья о том, как сделать в ЦФТ, то что мы привыкли делать в Бисквите. Начинал я ее писать только для себя, чтобы упорядочить свои знания. Однако потом оказалась, что эта тема важна для всех разработчиков, которые переходят на ЦФТ и не только с Бисквита. Именно поэтому я решил разместить ее на Хабре. Примеры из Бисквита будут курсивом, примеры из ЦФТ - жирные или виде блоков кода:

код

1.Сообщение в поток:

MESSAGE “Hello wolrd”.

debug_pipe('Hello wolrd', 0);

2.Сообщение в окно:

MESSAGE “Hello wolrd” VIEW-AS ALERT-BOX.

debug('Hello wolrd', 0);	

3. Операторы

+ (для цифр) +

+ (для строк) || (конкатенация)

= (для сравнения) =

= (для присваивания) :=

<> !=

В остальном так же.

 

4. Если

 if ….. then do:

               программный код        

 end.

else do:

                программный код

end.

 

5. Разбор строк через разделитель.

ENTRY

Inp_str			        string;  --строка для разбора с разделителем ‘;’
item_command	        rtl.string_table; --специальный массив 
item_command := STR_2.split(p_expression == Inp_str, p_delimiter == ';');

debug_pipe(item_command(1) , 0); -- Это как MESSAGE ENTRY(1…
debug_pipe(item_command(2) , 0); -- Это как MESSAGE ENTRY(2…

6. Выбор данных из базы.

6.1. Простой поиск уникальной записи в таблице (в ТБП).

FIND

Пример:

Поиск счета по номеру

s_ac_str string_100; --входная строка с номером счета
r_ac_fin ref [AC_FIN]; --ссылка на счет
r_AC_FIN := ::[AC_FIN]([MAIN_V_ID] = s_ac_str); --получение ссылки
debug_pipe(‘Имя счета: ‘ || r_AC_FIN.[NAME], 0);  --вывод в сообщение имени счета

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

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

Пример:

У нас есть ссылка на счет (нашли в предыдущем примере), а нам нужно вывести имя владельца счета.

Это имя лежит в другой таблице клиентов CLIENT, но нам не нужно там осуществлять поиск самой записи, достаточно написать так:

debug_pipe(r_AC_FIN. [CLIENT_V].[NAME], 0); 

В реквизите CLIENT_V на счете хранится ссылка на клиента - владельца счета.

6.2. Поиск первой записи с обработкой ситуации, когда запись не найдена.

FIND FIRST …… NO-ERROR.

….

IF NOT AVAILABLE

…..

6.3. Поиск последней записи.

FIND LAST

Аналогично, но вместо asc нужно писать desc.

 

6.4. Перебор всех записей по некоторому условию одной таблицы.

FOR EACH

Способ 1:

s_ac_str string_100; --входная строка с номером счета

for acc in ::[AC_FIN] all where acc.[MAIN_V_ID] = s_ac_str loop
		debug_pipe(‘Имя счета: ‘ || acc.[NAME], 0);
end loop;

Способ 2: 

s_ac_str string_100; --входная строка с номером счета
for (
	select acc(
		acc.[NAME] : C_NAME
		)
		in ::[AC_FIN] all
		where acc.[MAIN_V_ID] = s_ac_str
) loop
	debug_pipe(‘Имя счета: ‘ || acc.[C_NAME], 0);
end loop;

Примечание:

Слышал (но не проверял), что оба способа абсолютно одинаковы по быстродействию, поскольку код этих примеров Pl Plus во время компиляции преобразуется в одинаковый код Pl SQL.

 

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

FOR EACH….

                ,EACH…

 

Вывести имя счета и имя владельца.

for (
	select acc(
		acc.[NAME]  : C_NAME,
		cl.[NAME]   : C_NAME2
		)
		in ::[AC_FIN], (::[CLIENT] all : cl) all
		where acc.[MAIN_V_ID] = s_ac_str
		  and acc.[CLIENT_V]  = cl
) loop
	debug_pipe('Имена_: ' || acc.[C_NAME] || ' ' || acc.[C_NAME2], 0);		
end loop;

Примечание:  

Для выполнения этой задачи необязательно было работать с 2-мя таблицами, можно сделать запрос к одной  [AC_FIN] и использовать разыменованность.

Точно такой же результат выдаст такой код:         

for (
	select acc(
		acc.[NAME]  		   : C_NAME,
		acc.[CLIENT_V].[NAME]   : C_NAME2
		)
		in ::[AC_FIN] all
		where acc.[MAIN_V_ID] = s_ac_str
) loop
	debug_pipe('Имена2_ : ' || acc.[C_NAME] || ' ' || acc.[C_NAME2], 0);		
end loop;

7. Запуск одной операции из другой.

RUN oper.p (par1, par2).

 

Способ 1. В этом случае запускается только «тело» операции.     

::[tbp_oper].[oper](
	P_PAR1 == par1,
	P_PAR2 == par1
	);

Способ 2. В этом случае запускается вся операция вместе с формой.

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

stdio.put_line_buf('<% PLPCALL [TBP_OPER].[OPER](%PARAM%.P_PAR1 => %VAR%.PAR1, %PARAM%.P_PAR2 => %VAR%.PAR2) %>'); 

Переменные PAR1 и PAR2 должны быть заданы в свойствах вызывающей операции в разделе «Переменные».

 

Способ 3. Вызов из клиент-скрипта тоже всей операции.

Runtime.PlayEx("<% PLPCALL [TBP_OPER].[OPER](%PARAM%.P_PAR1 => %VAR%.PAR1, %PARAM%.P_PAR2 => %VAR%.PAR2) %>")

Примечание:

Также используется:

stdio.put_line_buf('<% CALL……. – запуск операции над конкретным экземпляром.

8. Инклюды

{globals.i}

pragma include(::[RUNTIME].[MACRO_LIB]);

9. Препроцессоры

&GLOBAL-DEFINE DELIM ";" /* разделитель для csv формата */

pragma macro(DELIM,                  ';'); -- разделитель для csv формата

Использовать в коде: &DELIM

 

10. NO-UNDO, NO-LOCK

Можно считать, что все переменные описываются как NO-UNDO.

 

bank.[DEPART]  --NO-LOCK

 

EXCLUSIVE-LOCK:

Способ 1.

bank=>[DEPART] –-блокировка одного реквизита записи

Способ 2.

bank%lock –-блокировка всей записи

 

Где bank – ссылка на банки.

Первый способ обращения к записи типа bank.[DEPART],  в отличие от NO-LOCK Бисквита, дает возможность изменить запись, поэтому bank=>[DEPART] применяется редко.

 

11. Транзакции

DO TRANSACTION:

………

END.

Для управления транзакциями существует ряд операторов:

commit;

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

savepoint < имя >;

Устанавливает точку отката с именем < имя >.

rollback [ to < имя > ]

Отменяет сделанные изменения, начиная от последнего commit или начиная с места, где была установлена точка отката с именем < имя >.

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

 

12. Обмен данными между базой и файлами в разных форматах.

12.1. Выгрузка в XLS.

Для вывода в XLS используется шаблон. Его необходимо подгрузить в справочник «Справочник шаблонов документов».

pragma macro(xl, '::[OOXML].[API_XL]'); --препроцессор
pragma macro(lib_ooxml,'::[SHABLON_DOC].[LIB_OOXML]'); --препроцессор
&lib_ooxml.xl_init(p_shablon_code == 'RSB_REP_KSPL'); -- вызов шаблона
&xl.Open_Sheet(1); -- установим нужный лист
&xl.put(3, 6, ‘информация для вывода в ячейку 3 6 листа 1’)); -- вывод в строку 3, колонку 6
&lib_ooxml.xl_finish; --вывод xls на экран

12.2 Выгрузка в word.

Для вывода в word используется шаблон. Его необходимо подгрузить в справочник «Справочник шаблонов документов».

pragma macro(wd, '::[OOXML].[API_DOC_SAX]'); --препроцессор
p_shablon_ref ref [SHABLON_DOC]; --переменная для ссылки на шаблон
v_tf_field constant.MEMO_TABLE_S; --массив для переменных шаблона
p_shablon_ref := ::[SHABLON_DOC]%locate(x where x.CODE = 'SHABLON_CODE'); --поиск шаблона по коду
v_report_data := ::[SHABLON_DOC].[LIB_OOXML].wd_init(
p_shablon_ref 	    == p_shablon_ref							,p_filial 		    == stdlib.userid().[FILIAL]						,p_stop_on_error == false); --подгрузка шаблона
	v_tf_field := &wd.GetDocVars; --подгрузка переменных из шаблона в массив
	v_tf_field('ДАТА'):= vDate_Str; --заполнение переменной ДАТА неким значением
	v_tf_field('ФИО'):= P_CLIENT; --заполнение переменной ФИО неким значением
	&wd.PutDocVars(v_tf_field); --возврат значений с массива на документ
	&wd.finish(v_report_data); --вывод документа на экран

12.3. Загрузка из файла csv (файл лежит на сервере, клиентом не выбирается, имя файла прописывается в операции)

--Необходимые переменные
ifile			integer;
ofile			[FILE$LOAD];	
sbuffer			varchar2(1000);
--Определим путь и имя файла
ofile.[SRC_PATH]	:= stdio.Get_Env('FIO_ROOT_DIR');
ofile.[SRC_NAME]	:= 'for_test.csv';
ofile.[SRC_TYPE]	:= true;

    	--Вычитали данные
    	ifile := stdio.open(ofile.[SRC_PATH], ofile.[SRC_NAME], 'r');
    	while stdio.get_line(ifile, sbuffer, True, stdio.WINTEXT, Null) loop
        		if sbuffer is not null then
            			sbuffer:= replace(replace(sbuffer, chr(10), ''), chr(13), ''); 
		--В переменной sbuffer строки из файла for_test.csv.
--Далее их можно обрабатывать, например разбирать
		--использую аналог функции ENTRY (пункт 5 из данный статьи)
        		end if;
   	 end loop;
    	stdio.f_close(ifile);

 

12.4. Загрузка из XLS (файл лежит на клиенте или сервере и выбирается пользователем).

У операции необходимо создать параметр P_FILE типа FILE$LOAD и переменные V_FILEPATH типа STRING_4000, V_FILENAME типа STRING_4000.

 P_FILE необходимо разместить на экранной форме.

 

В секции «Локальные описания» необходимо написать код:

                        

pragma macro (xl, '::[OOXML].[API_XL]'); --препроцессор для работы с xls
pragma macro (get, '&xl.getSheetRowColVal(
			p_row		== [1]		--Строка адреса ячейки
			,p_col		== [2]		--Колонка адреса ячейки
			,p_sheet	== 1		--Номер листа
			,p_tp		== v_s_tp		--Тип ячейки см. xl_tp_...
			,p_st		== v_n_st		--Индекс стиля ячейки
		)', substitute); --препроцессор для получения данных из ячейки xls	
v_s_tp				string(128); --переменная для &get
v_n_st				number; --переменная для &get
v_blob_file			blob := empty_blob(); --переменная для загрузки данных

В секции «Проверка» для p_message = 'VALIDATE' и p_info = 'OK' необходимо написать код:

if nvl(P_FILE.[SRC_TYPE], false) then
		P_FILE.[SRC_TYPE]		:= null;
		P_FILE.[DST_TYPE]		:= null;			
	end if;
	P_FILE.[DST_NAME]		:= P_FILE.[SRC_NAME];
              V_FILEPATH := P_FILE.[SRC_PATH];
              V_FILENAME := P_FILE.[SRC_NAME];

В секции «тело» разбор файла XLS:

::[SHABLON_DOC].[LIB_OOXML].read_from_file(								
    p_blob		== v_blob_file		--Содержимое файла (BLOB)
		,p_dir		== V_FILEPATH		--Путь к файлу на сервере
		,p_fname	== V_FILENAME	--Имя файла на сервере
,p_delete	== false			--Удаление файла на сервере после чтения
		); -- чтение файла в переменную v_blob_file				
if v_blob_file is null 
	then
		pragma error('Файл не выбран!');
	end if;	
	if not &xl.open_file(v_blob_file)
	then
		pragma error('Ошибка чтения файла!');
	end if;	
	&xl.open_sheet(1); --	открытие страницы 1 файла
	Debug_pipe(&get(6, 3), 0);  --данные из ячейки (строка 6,столбец 3,страница 1)	

13. А где в ЦФТ настроечные параметры и классификаторы?

                В справочнике Настройки. (FP_TUNE) и то, и другое.

 

14. А где с ЦФТ шедулер и бисмарк?

1.SYSTEM_JOBS (Выполнение заданий по расписанию)

В навигаторе настраивается в Система/Выполнение заданий по расписанию.

2.TEXT_JOBS (Выполнение текстовых заданий)

В навигаторе настраивается в Система/Выполнение текстовых заданий.

3.Справочник BGP(Фоновые процессы)

 

15. Временные таблицы.

                DEFINE temp-table

Как известно, функциональность временных таблиц Бисквита ничуть не хуже постоянных таблиц. Полноценного аналога в ЦФТ я не нашел.     Есть 3 аналога хуже.

Вариант 1. Тип TEMP_TABLE_S (Временная таблица).

Она так называется, а на самом деле это постоянная таблица. Она описана в базе.

Можно использовать, хотя это делают редко.

Вариант 2. Создать свою таблицу в базе данных и использовать ее как временную. Необходимо на закладке "Таблица" указывать параметр "Время жизни" = "Пропадет по окончании сессии". Тогда она гарантированно будет чиститься и не будет загрязнять БД.

Вариант 3. Массивы, описанные в операциях (применяются часто).

Массивы действительно временные, но функциональность их сильно ограничена по сравнению с temp-table Бисквита. В массивах всего 1 индекс, он может быть целочисленный или строковый. Нельзя осуществлять поиск по полю массива. Можно осуществить поиск по индексу или полный перебор.

Пример описания массива:

type strucType is record (    FileName      string, 
                      	      helloString   string, 
                              procName      string ); 
type rezType is table of  strucType index by integer; 
rez   rezType;

Как массив применять, описывать не буду – примеров огромное множество.

 

16. Показать табличные данные пользователю.

                Объекты browse, navigate.

 

 Способ 1. Представления.

Тема обширная, раскрывать не буду. Можно делать SQL-представления и писать для них запросы. Можно создавать простые представления, для них есть удобный конструктор. Простые представления можно конвертировать в SQL-представления при загрузке в Admin 2.0 (Eclipse).

 

Способ 2. GRID на форме операции.

Создать параметр операции как массив, например:

В качестве источника можно применить существующий в базе массив с данными или задействовать временную таблицу (пример 2, пункта 22 данной статьи).

 

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

 

 

17. Задать пользователю вопрос после запуска операции по кнопке «OK».

                Эта задача меня поразила тем, как легко она реализуется в Бисквите и как сложно в ЦФТ.

                Спасибо за пример и консультацию Олегу Опалихину.

                В Бисквите:

if is_question then DO:

    MESSAGE "Вы хотите продолжить?"

        VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE is_need_bl_process AS LOGICAL.

    if not is_need_bl_process then RETURN.

end.      

                В ЦФТ: 

1.       Создать переменную операции V_IS_QUESTION типа Логика

2.       Разместить переменную V_IS_QUESTION на форме операции. Поставить свойства переменной V_IS_QUESTION на форме Visible=False и ValidateName=V_IS_QUESTION. Поставить свойство кнопки OK на форме CheckValidate=True

3.       Поставить в дополнительных свойствах операции «при смене элемента управления»= «Сервер,Клиент».

4.       Написать в разделе операции «Клиент-скрипт» код на языке VBScript, который поднимет форму для вопроса пользователю:

Public Function Main(LastControl)
    Main = True
    If LastControl Is Ok Then
        Main = Null
        if Form1.ScriptServerValidate(Nothing, "QUESTION") then
            if V_IS_QUESTION then
                call Runtime.ShowMonitor
                if MsgBox("Вы хотите продолжить?", vbYesNo + vbQuestion + vbDefaultButton2, "Вопрос") = vbYes then
                    if Form1.ScriptServerValidate(Nothing, "PROCESS_BL_YES") then
                        Main = True
                    end if
                else
                    if Form1.ScriptServerValidate(Nothing, "PROCESS_BL_NO") then
                        Main = True
                    end if
                end if
            else
                Main = True
            end if
        end if
    End If
End Function

5.       В разделе «Локальные описания» задать переменную:

v_b_need_bl_process boolean;

6.       В разделе «Проверка» написать код:

begin
    if p_message = 'VALIDATE' then	
 	if p_info = 'QUESTION' then
            --Здесь некий код который определяет нужно ли задавать вопрос пользователю
	    V_IS_QUESTION := TRUE; --например нужно	
        elsif p_info = 'PROCESS_BL_YES' then
            v_b_need_bl_process := true;
        elsif p_info = 'PROCESS_BL_NO' then
            v_b_need_bl_process := false;
        end if;		
    end if;	
end;

7.       В разделе «Тело» написать код:

begin
    if not v_b_need_bl_process then
	return;
    end if;	
    --Здесь код, который выполняется если пользователь ответил «Да» 
    -- и не выполняется если пользователь ответил «Нет».		
    debug_pipe('Сработало!', 0); --например так
end;

       

Вопрос будет выглядеть так:

 

18. То, что нельзя сделать в Бисквите, но есть в ЦФТ.

1. В Бисквите практически нельзя создавать свои таблицы, поля для таблиц и индексы. Теоретическая такая возможность есть, но не практическая. В ЦФТ никаких проблем с этим нет. Создавайте столько, сколько нужно для ваших задач.

2. В Бисквите отсутствует полноценный отладчик. В ЦФТ в новой среде разработки Admin 2.0 появилась возможность запускать полноценный отладчик с точками останова и соответственно с просмотром значения всех переменных и во время остановки выполнения операции.

3.  Разыменованность. Об этом писал выше.

 

Заключение.

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

 Хочу выразить благодарность людям, благодаря которым данная статья появилась:

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

Саламову Камилю за непосредственный импульс в написании статьи, за ценные замечания к тексту статьи, за моральную поддержку и за постоянные напоминания, что нужно переходить на Admin 2.0 :)

Наумкину Анатолию за организацию обучения на учебном портале ЦФТ.

Миниярову Ринату, Давыдову Денису и Опалихину Олегу за ответы на вопросы в начале моего обучения ЦФТ и за примеры исходного кода.

Руфееву Максиму за внимательность. За то, что заметил мою ошибку в слове elsif :)

Шершову Андрею на помощь в публикации на Хабре.

 

Источник: https://habr.com/ru/company/rshb/blog/566964/


Интересные статьи

Интересные статьи

Власть в блоге Технократии переходит андроид-разработчикам. Владислав Титов рассказывает про то, как добиться непрерывающегося UI при смене локализации. Чи...
Чувствую, что должен написать статью про будущее Биткоина и криптовалют, не часто бывает время, когда людям это интересно. И сразу сообщу главное. Нет вообще никаких прич...
Одна из самых важных (на мой взгляд) функций в Битрикс24 это бизнес-процессы. Теоретически они позволяют вам полностью избавиться от бумажных служебок и перенести их в эл...
У нашей компании есть свой игровой движок, который используется для всех разрабатываемых игр. Он предоставляет всю важную базовую функциональность:  рендеринг; работа с SDK; работа с о...
Всем привет, меня зовут Денис, мы разрабатываем сервис по аналитике подписок iOS-приложений – Apphud. На WWDC 2019 Apple представила новый способ взаимодействия с интерфейсом вашего приложения: ...