Описание формы

За написание и тестирование скриптов (как ДС, так и ВДС) отвечает модуль TestingScripts.pas с соответствующим классом TTestingScripts

В этом классе для определения вида скрипта отвечает поле scriptType. Это поле в дальнейшем отвечает за всё остальное поведение формы.

В зависимости от вида скрипта открывается та или иная страница на pcTypeScript, содержащие в себе нужные сценарии проверки.

Все страницы взаимодействуют с основными контролами класса TAgbMemo - поле ввода mScript и результат его работы mResult.

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

Отработка написанных скриптов

При выполнении скриптов ДС-ВДС (как для проверки, так и в режиме работы из заказа) задействуются функции из модуля UDiscountTools. Все расчёты в конечном счёте приведут именно к ним.

// выполняет скрипт расчёта скрипта ВДС;
function ExecuteExternalDiscScript(doc_id: int64; script: string; var errScripts: string; AUpTr: TpFIBTransaction; out ExtScriptResInfo: TExtScriptResInfo): Double; 
// выполняет скрипт расчёта скрипта ДС.
function ExecuteSimpleDiscScript(AContrID, ATovarID:int64; Control:Integer; ASchemeID: variant; showException: boolean ;
                                 script, Dat: string; var errScripts: string; ARdTr, AUpTr: TpFIBTransaction):double; 

Основная разница у них в перечне входных и выходных параметров.

Скрипт ВДС передаёт принимает 1 параметр doc_id (ВнНомер документа заказа), возвращает количество бонусов для начисления и целый блок дополнительной информации, содержащийся в ExtScriptResInfo.

Скрипт ДС передаёт принимает 3 параметра - ID клиента, ID товара и ссылку на контрол из формы заказа. Возвращает размер скидки на конкретную позицию по клиенту.

Общий принцип их работы таков:

1. В модуле UDiscountTools Создаётся объект класса TScriptExecutor se, в конструктор передаётся записывающая транзакция;

se := TScriptExecutor.Create(_UpTr);

2. В модуле ScriptFunctions Конструктор TScriptExecutor создаёт объект PSScript паскалевского скриптера класса TPSScript из библиотеки uPSComponent;

PSScript:=TPSScript.Create(Nil);

3. В модуле UDiscountTools Запускается попытка скомпилировать скрипт se.CompileScript(script, true, errScripts). В случае неудачного выполнения будет возвращен False, а errScripts будет содержать в себе список ошибок;

if not se.CompileScript(script, true, errScripts) then
          raise EExternal.Create('Ошибка выполнения скрипта расчёта скидки!');

4. В модуле ScriptFunctions При компиляции в объект PSScript передаётся текст скрипта и запускается его компиляция:

PSScript.Script.Text:=Script;
Result:=PSScript.Compile;

5. В модуле ScriptFunctions В случае неуспешной компиляции объект PSScript будет иметь в себе заполненные поля PSScript.CompilerMessageCount (количество сообщений) и коллекцию PSScript.CompilerMessages сами сообщения об ошибках), из которых потом и собирается итоговая строка со списком ошибок;

if not Result then
begin
  for i:=0 to PSScript.CompilerMessageCount-1 do
  begin
    if showMSG then
      SimpleMessage(PSScript.CompilerMessages[i].MessageToString);

    if messages = '' then
      messages := PSScript.CompilerMessages[i].MessageToString
    else
      messages := messages + #13#10 + PSScript.CompilerMessages[i].MessageToString;
  end;
end;

6. В модуле UDiscountTools определяется наличие заголовка GetDiscount:

ProcNo := se.PSScript.Exec.GetProc('GetDiscount');
  if ProcNo = InvalidVal then
    errScripts := 'Unknown procedure GetDiscount'

7. В модуле UDiscountTools если такой заголовок был найден (ProcNo <> InvalidVal), то пытается выполниться сам скрипт с переданным в него массивом входящих параметров (на примере ДС). Выполнение делается в try..except, а ошибки при выполнении записываются в errScripts :

try
  Result := se.PSScript.ExecuteFunction([AContrID,ATovarID,Control],'GetDiscount');
except
  on E: EFIBError do
    errScripts := E.IBMessage;
  on E:Exception do
    errScripts := E.Message;
end;

Какими функциями и процедурами можно пользоваться при написании скриптов

После создания объекта PSScript ему, в конструкторе класса TScriptExecutor, присваиваются обработчики для импорта тех или иных функций. Это делается так:

PSScript.OnExecImport:=ClassesPluginExecImport; - здесь регистрируются классы, типы и методы, которые используются в рантайме;
PSScript.OnCompImport:=ClassesPluginCompImport; - здесь регистрируются базовые классы, типы и методы, которые используются при компиляции.

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

Регистрация методов и функций для рантайма делается так:

exec.RegisterDelphiMethod(Self, @TScriptExecutor.FastSQLVal,'FastSQLVal', cdRegister);
exec.RegisterDelphiFunction(@DaysBetween,'DaysBetween', cdRegister);

Регистрация остальных, кастомных методов и функций для компиляции делается в обработчике TScriptExecutor.PSScriptCompile:

Sender.AddMethod(Self, @TScriptExecutor.FastSQLVal,'Function FastSQLVal(Data: string):Variant');
Sender.AddFunction(@DaysBetween,'function DaysBetween(ANow, AThen: TDateTime): Integer');

Все базовые функции и методы, на которые мы ссылаемся при регистрации, должны иметь реализацию в своих соответствующих модулях, доступ к которым должен быть из класса TScriptExecutor. Так, функция DaysBetween объявлена в модуле DateUtils.

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

https://doc.agb.is/special_params_in_scripts

Пройти тест можно по ссылке:

https://forms.gle/EuUzReSkCwNv5PH78