Příklady pokročilého skriptování
Tato kapitola doplňuje obecný úvod do problematiky skriptování v systému ABRA Gen o ukázky pokročilých praktických příkladů.
Pokročilé příklady skriptování
Příklady skriptování pro pokročilé

Funkce SQLSelectFirst slouží na vrácení prvního řádku výsledku SQL dotazu.
Konkrétně se jedná o funkce SQLSelectFirstAsString , SQLSelectFirstAsInteger, SQLSelectFirstAsExtended, SQLSelectFirstAsBlob
Příklad použití:
..
mBlob:=Self.ObjectSpace.SQLSelectFirstAsBlob('SELECT NOTE FROM FIRMS'); //Vrátí hodnotu jako TBytes
mString:=Self.ObjectSpace.SQLSelectFirstAsString('SELECT NAME FROM FIRMS'); //Vrátí hodnotu jako String
mInt:=Self.ObjectSpace.SQLSelectFirstAsInteger('SELECT CODE FROM FIRMS'); //Vrátí hodnotu jako Integer
mExt:=Self.ObjectSpace.SQLSelectFirstAsExtended('SELECT PENALTYPERCENT FROM FIRMS'); //Vrátí hodnotu jako Extended
//Hodnoty po provedení funkce jsou následující
//mBlob = ABRA Software a.s. je technologická firma...
//mString = ABRA Software a.s.
//mInt = 00001
//mExt = 1,25
..

Na převod času na UnixTime slouží funkce DateTimeToUnix.
Příklad použití:
..
mDateTimeNow1:= DateTimeToUnix(Now(), True); //vrací výsledek UnixTime s časovým posunem
mDateTimeNow2:= DateTimeToUnix(Now(), False); //vrací výsledek UnixTime v UTC
//Hodnoty po provedení funkce jsou následovný
//mDateTimeNow1 = 1665079779
//mDateTimeNow2 = 1665072579
..

Od verze 19.4.3 je možné používat parametrizované SQL dotazy ze skriptování. Používání parametrů v SQL je snadnější, bezpečnější a pro SQL servery i rychlejší na vykonání díky cachování plánů SQL dotazů. Přes parametry lze snadno a bezpečně přenést libovolný datový typ tedy i binary blob data.
Příklad použití parametrů při vykonávání SQL dotazů (SQLExecute):
UPDATE GlobData SET DueTerm = :DueTerm, Logo = :Logo
..
mInputParams := TNxParameters.Create;
try
mInputParams.NewFromDataType(dtInteger, 'DueTerm').AsInteger := 10;
mRawParameter := TNxRawParameter(mInputParams.NewFromDataType(dtVarBytes, 'Logo'));
mStream := TMemoryStream.Create;
try
mStream.SetBytes(cBytes);
mRawParameter.LoadDataFromStream(mStream);
finally
mStream.Free;
end;
mRowsAffected := ObjectSpace.SQLExecute('UPDATE GlobData SET DueTerm = :DueTerm, Logo = :Logo', mInputParams);
FxCheckValue_Integer(mRowsAffected, 1, 'nebyl modifikován jeden záznam');
finally
mInputParams.Free;
end;
..
Příklad použití parametrů při získávání dat z databáze (SQLSelect, SQLSelect2):
SELECT DueTerm FROM GlobData WHERE ID = :ID
..
mInputParams := TNxParameters.Create;
try
mInputParams.NewFromDataType(dtString, 'ID').AsString := cCompanyGlobDataID;
mSQLResult := TStringList.Create;
try
ObjectSpace.SQLSelect('SELECT DueTerm FROM GlobData WHERE ID = :ID', mSQLResult, mInputParams);
FxCheckValue_String(mSQLResult.Text, '10' + #13#10, 'nedošlo k modifikaci dat nebo došlo k chybnému přečtení');
finally
mSQLResult.Free;
end;
mMemTable := TMemTable.Create(nil);
try
ObjectSpace.SQLSelect2('SELECT DueTerm, Logo FROM GlobData WHERE ID = :ID', mMemTable, mInputParams);
FxCheck(mMemTable.RecordCount = 1, 'mMemTable.RecordCount <> 1');
mMemTable.First;
mMemTable.Next;
FxCheckValue_Integer(mMemTable.FieldByName('DueTerm').AsInteger, 10, 'DueTerm <> 10');
FxCheckValue_TBytes(mMemTable.FieldByName('Logo').AsBytes, cBytes, 'Logo <> 0x0102...FF');
finally
mMemTable.Free;
end;
finally
mInputParams.Free;
end;
..

Funkce FindPaymentDestByVarsymb slouží na na dohledání placeného dokladu.
function NxFindPaymentDestinationsByVarSymb(
AObjectSpace: TNxCustomObjectSpace;
ACredit: Boolean; AVarSymbol: string; ADocTypes, AOIDS: TStrings;
AFlag, AFlagForOneFound: Integer; AAmount: Extended; ACurrency_ID: String;
AFirm_ID: TNxOID)';
Popis parametrů:
// AObjectSpace ObjectSpace
// ACredit True/False typ platby Kredit/Debet
// AVarSymbol Hledaný variabilní symbol
// ADocTypes TStrings do kterých funkce vrací typ dohledaných dokladů
// AOIDS TStrings do kterých funkce vrací OID dohledaných dokladů
// AFlag Příznak jak dohledávat podle částky a zaplacení
// -1 - Vrátit vše vyhovující dle VS bez ohledu na stav zaplacení a částku - ignoruje nastavení parametrů ve firemních údajích
// 0 - Vrátit vyhovující dle VS, částky a stavu zaplacení s ohledem na nastavení parametrů ve firemních údajích
// AFlagForOneFound Příznak jak dohledávat podle částky
// 0 - Musí vyhovovat jen VS
// 1 - Musí vyhovovat VS i částkaa měna placeného dokladu
// AAmount Částka - když je 0, nebere se při vyhledávání na částku zřetel
// ACurrency_ID Měna
// AFirm_ID Firma - pokud je vyplněno, při vyhledávání se bere zřetel i na firmu na dokladu
Příklad použití:
procedure find_payment_destination_by_varsymb(
ACredit: Boolean; AVarSymb: string; AAmount: EWxtended;
mFlag, mFlagForOneFound: Integer;
ACurrency_ID, AFirm_ID: TNxOID);
var
mObjectSpace: TNxCustomObjectSpace;
mDocTypes, mOIDs: TStrings;
begin
...
mOIDs := TStringList.Create;
try
mDocTypes := TStringList.Create;
try
NxFindPaymentDestinationsByVarSymb(
mObjectSpace, ACredit, AVarSymb, mDocTypes, mOIDS, AFlag, AFlagForOneFound,
AAmount, ACurrency_ID, AFirm_ID);
// V StringListech mDocTypes a mOIDS jsou vráceny identifikace dohledaných dokladů (Typ dokladu a OID)
...
finally
mDoctypes.Free;
end;
finally
mOIDs.Free;
end;
end;

Jde o následující metody třídy TNxCustomAccountedDocument:
function AttachSourceGroup(const ASourceGroup_ID: TNxOID);
function RemoveSourceGroup(const ASourceGroup_ID: TNxOID);
function IsSourceGroupAttached(const ASourceGroup_ID: TNxOID): Boolean;
function GetSourceGroup: TNxOID;
Příklad použití:
procedure pair_it;
var
mReceivedInvoice, mReceiptCard: TNxCustomBusinessObject;
mSourceGroup_ID: TNxOID;
begin
...
// Připojení dokladu do ručního párování
// Zde připojujeme existující příjemku do párovací skupiny faktury přijaté
mSourceGroup_ID := TNxCustomAccountedDocument(mReceiptCard).GetSourceGroup;
TNxCustomAccountedDocument(mReceivedInvoice].AttachSourceGroup(mSourceGroup_ID);
mReceivedInvoice.Save; // !!! POZOR - Připojení se projeví až po Save objektu
...
// Odpojení dokladu z ručního párování
// Zde odpojujeme již připojenou příjemku z párovací skupiny faktury přijaté
mSourceGroup_ID := TNxCustomAccountedDocument(mReceiptCard).GetSourceGroup;
TNxCustomAccountedDocument(mReceivedInvoice].RemoveSourceGroup(mSourceGroup_ID);
mReceivedInvoice.Save; // !!! POZOR - Odpojení se projeví až po Save objektu
...
// Zjištění, zda je dokument připojen v ručním párování
// Zde zjišťujeme zda je příjemka připojena do párovací skupiny faktury přijaté
mSourceGroup_ID := TNxCustomAccountedDocument(mReceiptCard).GetSourceGroup;
if TNxCustomAccountedDocument(mReceivedInvoice].IsSourceGroupAttached(mSourceGroup_ID) then
begin
...
end;
...
end;

Ukázkový skript, který přidá řádek do rozeditovaného dokladu:
{
Přidání řádku do rozeditovaného dokladu
}
procedure InsertRow(Sender : TButton);
var
mSite: TSiteForm;
mControl: TControl;
mDataset: TNxRowsObjectDataSet;
mRow: TNxCustomBusinessObject;
begin
try
mSite := TComponent(Sender).Site;
mControl:= mSite.FindChildControl('tabRows.grdRows');
mDataset := TNxRowsObjectDataSet(TMultiGrid(mControl).DataSource.DataSet);
if Assigned(mDataset) then
begin
mDataSet.DisableControls;
mRow := mDataSet.CreateBusinessObject;
mRow.Prefill;
mRow.SetFieldValueAsInteger('RowType',3);
mRow.SetFieldValueAsInteger('PosIndex',1);
mRow.SetFieldValueAsString('Store_Id','2100000101');
mRow.SetFieldValueAsString('Division_ID','2100000101');
mRow.SetFieldValueAsString('Storecard_Id','2100000101');
mRow.SetFieldValueAsFloat('Quantity', 1);
end;
finally
TDynSiteForm(mSite).ActiveDataSet.UpdateFields; //Aby se o změně dozvěděl hlavičkový dataset
mDataset.RefreshAndRestoreLastSelectedItem;
mDataSet.EnableControls;
end;
end;
{
Vyvolává se po vytvoření instance formuláře.
}
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
mMAction: TMultiAction;
begin
// Vytorime novou jednoduchou akci
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Přidání řádku';
mAction.Hint := 'Přidání řádku a aktualizace datasetu';
mAction.Category := 'tabDetail';
mAction.OnExecute := @InsertRow;
end;
begin
end.

1. Ukázkový skript, který umožňuje rozšířit seznam objektů na záložce X-vazby, a to jak přidáním objektů, tak i celých nových skupin objektů:
{
Umožňuje rozšířit seznam objektů na záložce X-vazby, a to jak přidáním objektů, tak i celých nových skupin objektů. Doporučujeme přidávat objekty na obě strany, tedy jak na zdrojový, tak cílový objekt, poté uživatel uvidí např. na objektu typu A objekty typu B a také naopak z B uvidí A.
Ukázka skriptu rozšiřující X-vazby business objektu Sklad o skupinu Nabídky vydané a v ní všechny záznamy s pořadovým číslem 1.
A dále o skupinu Skladové karty se záznamy s kódem začínajícím na 0.
}
procedure AddLinks(Self: TNxCustomBusinessObject; const AGroups, AObjects: TStringList; const ASite: TSiteForm);
var
mInputParameters: TNxParameters;
mIDs: TStringList;
I: Integer;
begin
AGroups.Add(Class_IssuedOffer + '=' + 'Nabídky vydané');
mInputParameters := TNxParameters.Create;
try
mInputParameters.NewFromDataType(dtInteger, 'OrdNumber').AsInteger := 1;
mIDs := TStringList.Create;
try
Self.ObjectSpace.SQLSelect('SELECT ID FROM IssuedOffers WHERE OrdNumber = :OrdNumber AND Revided_ID IS NULL', mIDs, mInputParameters);
for I := 0 to mIDs.Count - 1 do begin
AObjects.Add(Class_IssuedOffer + '=' + mIDs[I]);
end;
finally
mIDs.Free;
end;
finally
mInputParameters.Free;
end;
AGroups.Add(Class_StoreCard + '=' + 'Skladové karty');
mInputParameters := TNxParameters.Create;
try
mInputParameters.NewFromDataType(dtString, 'StartWith').AsString := '0%';
mIDs := TStringList.Create;
try
Self.ObjectSpace.SQLSelect('SELECT ID FROM StoreCards WHERE Code LIKE :StartWith AND Hidden = ''N''', mIDs, mInputParameters);
for I := 0 to mIDs.Count - 1 do begin
AObjects.Add(Class_StoreCard + '=' + mIDs[I]);
end;
finally
mIDs.Free;
end;
finally
mInputParameters.Free;
end;
// Lze použít i objekty definovatelných číselníků
//AGroups.Add('1NPNI4M2JIVOFBV23CHPHFPN5W=Eshop - Číselníkové hodnoty vlastnosti skladové karty');
//AObjects.Add('1NPNI4M2JIVOFBV23CHPHFPN5W=1000000101');
// Ukázka prázdné skupiny
//AGroups.Add(Class_IssuedOrder + '=Objednávky přijaté');
//AGroups.Add(Class_Division + '=Střediska');
end;
begin
end.
2. Ukázkový skript, který umožňuje upravit poznámku zobrazovanou ve vizuálnu na záložce X-vazby.:
{
Ukázka skriptu pro business objekt Skladová karta. Na všech X-vazbách skladových karet přidá do poznámky EAN. V X-vazbách agendy Sklady navíc ještě přidává množství dle vybraného skladu.
}
procedure GetLinkDescription2_Hook(Self: TNxCustomBusinessObject; var Result: String; const ASite: TSiteForm);
var
mObjectSpace: TNxCustomObjectSpace;
mStoreID: String;
mQuantity: Double;
begin
if (ASite <> nil) then begin
if (ASite.GetSiteCLSID = Site_Stores) and (TBusRollSiteForm(ASite).CurrentObject <> nil) then begin
mObjectSpace := TBusRollSiteForm(ASite).BaseObjectSpace;
mStoreID := TBusRollSiteForm(ASite).CurrentObject.GetFieldValueAsString('ID');
mQuantity := mObjectSpace.SQLSelectFirstAsExtended('SELECT Quantity FROM StoreSubCards WHERE Store_ID = ' +
QuotedStr(mStoreID) + ' AND StoreCard_ID = ' + QuotedStr(Self.GetFieldValueAsString('ID')));
Result := NxTrim(Result + '; Quantity: ' + FloatToStr(mQuantity), ';');
end;
end;
if Self.GetFieldCode('EAN') > 0 then
Result := NxTrim(Result + '; EAN: ' + Self.GetFieldValueAsString('EAN'), ';');
end;
begin
end
3. Příklad založení uživatelské vazby ze skriptování:
{
Ukázka části skriptu na vytvoření X-vazby v tabulce UserXLink mezi záznamy agend Objednávky přijaté -> Došlá pošta. Záznamy jsou označeny jako systémové.
}
mUserXLink := ObjectSpace.CreateObject(Class_UserXLink);
try
mUserXLink.New;
mUserXLink.Prefill;
mUserXLink.SetFieldValueAsString('SourceCLSID', Class_ReceivedOrder);
mUserXLink.SetFieldValueAsString('Source_ID', mReceivedOrderID);
mUserXLink.SetFieldValueAsString('DestinationCLSID', Class_PDMReceivedDoc);
mUserXLink.SetFieldValueAsString('Destination_ID', mPDMReceivedDocID);
mUserXLink.SetFieldValueAsBoolean('DisplayAsSystem', True);
mUserXLink.Save;
finally
mUserXLink.Free;
end;

Háček umožňuje přímo ovlivnit SQL. Kdykoliv číselník potřebuje data z databáze, sestavuje se SQL. Háček se volá před sestavením SQL.
procedure OnSelectSQL_Hook(Self: TNxBusinessRoll; AParams: TNxParameters; ADSQL: TRollDynamicSQL; AKind: TRollOnSelectSQLKind);
//Self
//Číselník, pro který se háček vyvolal
//AParams
//Parametry, se kterými se číselník zavolal. AParams typicky připravuje CompleteRollValidateParams háček na BO. AParams jsou jenom pro čtení.
//ADSQL
//Prostředník pro úpravu SQL.
//AKind
//Informace, z jaké části číselníku se háček vyvolal
//sskPage - při získání stránky s daty
//sskWhisperer - při zobrazení našeptávače
//sskExists - Služba číselníku - LookUp
//sskOIDByPart - Služba číselníku - FindPart
//sskOID - Služba číselníku - CheckOnly
//sskAllID - Služba číselníku - GetIds
//sskOIDByPrefill - Služba číselníku - Prefill
//sskCorrectSelected - Služba číselníku - CorrectSelected'
Příklad:
Chceme, aby uživatel Daniel Rasák (‘4300000101’) viděl pouze firmy s názvem začínajícím na A. (Jednoduchý příklad nad demodaty)
procedure OnSelectSQL_Hook(Self: TNxBusinessRoll; AParams: TNxParameters; ADSQL: TRollDynamicSQL; AKind: TRollOnSelectSQLKind);
begin
if NxGetActualUserID(Self.ObjectSpace) = '4300000101' then
begin
// do WHERE doplníme SQL podmínku
ADSQL.Where.Add('A.Name LIKE ''A%''');
end;
end;
Když není dostupný zdroj dat (tabulka), mohu si současně s podmínkou zdroj dat přidat:
Název aliasu volte s rozumem, nesmí kolidovat s existujícími.
Dejte si pozor, aby join nezmnožil řádky místo toho, aby je omezil.
procedure OnSelectSQL_Hook(Self: TNxBusinessRoll; AParams: TNxParameters; ADSQL: TRollDynamicSQL; AKind: TRollOnSelectSQLKind);
begin
ADSQL.Joins.Add('MT', 'join MyTable MT on MT.ID = A.Tab_ID', 'MT.Code LIKE ''A%''')
end;
Ve většině případů vystačíte s výše uvedenými příklady.
Máme ale k dispozici ještě jednu variantu, která je složitá na pochopení, ale v určitých případech může zásadně ovlivnit rychlost provedení dotazu.
Jádro ji používá např. pro FULLTEXT.
Zkuste použít, pokud základní WHERE anebo JOIN+WHERE budou pomalé.
procedure OnSelectSQL_Hook(Self: TNxBusinessRoll; AParams: TNxParameters; ADSQL: TRollDynamicSQL; AKind: TRollOnSelectSQLKind);
begin
ADSQL.Joins.Tweak.Add('select ID as Parent_ID from table');
end;
Poddotaz musí splňovat tato kritéria:
- vracet jeden sloupec s názvem Parent_ID
- Parent_ID musí být ID z číselníku, ze kterého se získávají data (podmínka omezuje proti A.ID
- hodnoty Parent_ID musí být unikátní

Příklad:
- rozpoznání místa objednávka přijatá řádek s ukázkou předání hodnoty z jiného pole
- rozpoznání X položky na řádku objednávky přijaté
- rozpoznání, že se jedná o vizuální editaci z definovatelného formuláře
Háček: Business objekt: Objednávka přijatá - řádek
procedure CompleteRollValidateParams_Hook(Self: TNxCustomBusinessObject; AFieldCode: integer; AParams: TNxParameters);
begin
// Doplnění identifikace položky, pro kterou je číselník používán a tím umožnění v háčku OnSelectSQL_Hook číselníku zohlednění daného místa.
if AFieldCode = Self.GetFieldCode('StoreCard_ID') then begin
AParams.GetOrCreateParam(dtString, 'ObjPri_StoreCard_ID');
// Navíc ukázka předání hodnoty jiného pole, které ovlivní výsledné omezení.
AParams.GetOrCreateParam(dtString, 'ObjPri_Store_IDValue').AsString := Self.GetFieldValueAsString('Store_ID');
end;
if AFieldCode = Self.GetFieldCode('X_StoreCard_ID') then begin
AParams.GetOrCreateParam(dtString, 'ObjPri_X_StoreCard_ID');
end;
end;
Háček: Číselník: Číselník skladových karet
procedure OnSelectSQL_Hook(Self: TNxBusinessRoll; AParams: TNxParameters; ADSQL: TRollDynamicSQL; AKind: TRollOnSelectSQLKind);
const
cStorePraha = '2100000101';
begin
if AParams.ParamExist('ObjPri_StoreCard_ID') and
(AParams.GetOrCreateParam(dtString, 'ObjPri_Store_IDValue').AsString = cStorePraha) then begin
// Příklad jen jednoduché karty, kterým název začíná na písmeno A
ADSQL.Where.Add('A.Category = 0 AND A.Name LIKE ''A%''');
end;
if AParams.ParamExist('ObjPri_X_StoreCard_ID') then begin
// Příklad jen karty se sériovými čísly
ADSQL.Where.Add('A.Category = 1');
if AParams.ParamExist('ObjPri_X_StoreCard_ID_FromDefForm') then begin
// Omezení jek pokud je pole editováno z definovatelného formuláře. Tzn. omezení za název nebude použit pro tvorbu např. z API
ADSQL.Where.Add('A.Name LIKE ''B%''');
end;
end;
end;
Doplnění parametru z definice definovatelného formuláře.

Příklad použití třídy CFxLog s metodou SaveLog, která vytvoří a uloží nový log v agendě Logy:
..
var
mLog: CFxLog;
mContext: TNxContext;
mCustomObjectSpace: TNxCustomObjectSpace;
begin
mCustomObjectSpace := Self.BaseObjectSpace;
mContext := NxCreateContext(mCustomObjectSpace);
mLog.SaveLog(mContext, 'LOGIE', 'Test_01', 'Poznámka_01', 0, Now);
mLog.SaveLog(mContext, 'LOGIE', 'Test_02', 'Poznámka_02', 1, Now);
mLog.SaveLog(mContext, 'LOGIE', 'Test_03', 'Poznámka_03', 2, Now);
..

Příklad jak otevřít číselník a vybranou hodnotu zpracovat:
// Vyloučení některých záznamů
//mParams.GetOrCreateParam(dtString, '_Excluded').AsString := '1J50000101;M100000101';
// Omezení jen na seznam povolených záznamů
//mParams.GetOrCreateParam(dtString, '_Allowed').AsString := '1J50000101;M100000101';
procedure SelectOneFromRoll(Sender : TObject);
var
mSite: TSiteForm;
mSiteContext: TNxContext;
mParams: TNxParameters;
mID: String;
begin
mSite := TComponent(Sender).Site;
mSiteContext := mSite.SiteContext;
mParams := TNxParameters.Create;
try
mParams.GetOrCreateParam(dtString, '_ID').AsString := 'O100000101'; // Otevření na konkrétním záznamu. '' - bez určení
if NxShowRoll(mSiteContext, Roll_StoreCards, mParams, 0, '', mSite.GetParentForm) then begin
mID := mParams.ParamByName('_ID').AsString;
ShowMessage('Vybraný záznam: ' + mID);
end;
finally
mParams.Free;
end;
end;
procedure MultiSelectFromRoll(Sender : TObject);
var
mSite: TSiteForm;
mSiteContext: TNxContext;
mParams: TNxParameters;
mIDs: String;
mSelectedList: TNxParameters;
i: Integer;
begin
mSite := TComponent(Sender).Site;
mSiteContext := mSite.SiteContext;
mSelectedList := TNxParameters.Create;
try
mParams := TNxParameters.Create;
try
// Označení vybraných záznamů
mSelectedList.GetOrCreateParam(dtString, '4100000101').AsString := '4100000101';
mSelectedList.GetOrCreateParam(dtString, 'E100000101').AsString := 'E100000101';
mParams.GetOrCreateParam(dtObject, '_SelectedList').AsObject := mSelectedList;
if NxShowRoll(mSiteContext, Roll_StoreCards, mParams, 0, '', mSite.GetParentForm) then begin
mIDs := '';
for i := 0 to mSelectedList.Count - 1 do
begin
mIDs := mIDs + mSelectedList.Params[i].AsString + ';';
end;
ShowMessage('Vybrané záznamy: ' + mIDs);
end;
finally
mParams.Free;
end;
finally
mSelectedList.Free;
end;
end;

Příklad použití funkce GetARESCZData popř. GetSKFirmData - vytvoření business objektu firmy a zavolání příslušných funkcí na naplnění (k vyzkoušení ve vizuálnu s interakcí uživatele):
procedure OnExecute_FillByOrgIdentNumber(Sender: TObject);
var
mSite: TSiteForm;
mFirm: TNxCustomBusinessObject;
mErrText: string;
mOrgIdentNumber: string;
begin
mSite := TComponent(Sender).BusRollSite;
mFirm := mSite.BaseObjectSpace.CreateObject(Class_Firm);
mFirm.New;
mFirm.Prefill;
//zadání IČO k otestování
mOrgIdentNumber := InputBox('IČO pro vyhledání firmy', 'Zadejte IČO:', '00000000');
//naplnění firmy z portálu ARES (CZ)
mFirm.SetFieldValueAsString('OrgIdentNumber', mOrgIdentNumber);
mErrText := '';
TNxFirm(mFirm).GetARESCZData(mErrText, True);
if mErrText = '' then
ShowMessage(mFirm.GetFieldValueAsString('Name'));
else
ShowMessage(mErrText);
//naplnění firmy z veřejné databáze (SK)
mFirm.SetFieldValueAsString('OrgIdentNumber', mOrgIdentNumber);
mErrText := '';
TNxFirm(mFirm).GetSKFirmData( mErrText, True);
if mErrText = '' then
ShowMessage(mFirm.GetFieldValueAsString('Name'));
else
ShowMessage(mErrText);
end;

V nástroji ScriptDebugger existuje možnost ve skriptu ručně vyvolat výjimku z breakpointů nebo za přímého běhu.
Po stisku tlačítka Vyvolat výjimku a potvrzení dialogu, kde lze změnit text výjimky se zašle do Abry požadavek na budoucí výjimku. Abra při příštím vykonání uměle selže.
Oproti vyvolání výjimky přímo ze skriptování příkazem RaiseException se ovšem ale nezobrazuje CallStack kde chyba vznikla.

Uvedený příklad demonstruje, jak lze na základě logiky v business objektu (objednávky přijaté) - po nastavení řady dokladu - docílit zapnutí/vypnutí řádkové slevy, a následně zobrazení/skrytí sloupců v gridu řádků té objednávky:
//v BO po vyplnění určité řady chceme, aby se aktivovala sleva
procedure AfterSetFieldValue_Hook(Self: TNxCustomBusinessObject; AFieldCode: Integer; AValue: TNxParameter; AOriginalValue: TNxParameter);
begin
if AFieldCode = 11000 then //řada dokladů
begin
if AValue.AsString = '1OU0000101' then //uvedená hodnota může reprezentovat např. řadu"OP" - tohle vyplyne z nějaké x položky nebo nějak jinak
begin
Self.SetFieldValueAsBoolean('IsRowDiscount', True)
end
else
begin
Self.SetFieldValueAsBoolean('IsRowDiscount', False)
end
end;
end;
//ve vizuálnu budeme potřebovat globální proměnnou
var
fSite: TSiteForm;
//ve vizuálnu vytvoříme proceduru pro zpracování události změny na řadě dokladu
procedure InitSite_Hook(Self: TSiteForm);
var
mpnDocQueue_ID: TNxGeneralRollMovablePanel;
begin
mpnDocQueue_ID := TNxGeneralRollMovablePanel(Self.FindComponent('mpnDocQueue_ID'));
mpnDocQueue_ID.onInEditAdmit := @DQEditAdmit; //Vytvoření procedury pro zpracování události zadání té řady dokladů
fSite := Self;
end;
//a v rámci té události si ve vizuálnu nastavíme, co potřebujeme - tady to je zobrazení popř. skrytí sloupců v gridu řádků
procedure DQEditAdmit;
var
mpnIsRowDiscount: TNxCheckBoxMovablePanel;
colRowDiscount1: TNxMultiGridColumn;
colRowDiscount2: TNxMultiGridColumn;
colRowDiscount3: TNxMultiGridColumn;
begin
mpnIsRowDiscount := TNxCheckBoxMovablePanel(fSite.FindComponent('mpnIsRowDiscount'));
colRowDiscount1 := TNxMultiGridColumn(fSite.FindComponent('colRowDiscount1'));
colRowDiscount2 := TNxMultiGridColumn(fSite.FindComponent('colRowDiscount2'));
colRowDiscount3 := TNxMultiGridColumn(fSite.FindComponent('colRowDiscount3'));
colRowDiscount1.Visible := mpnIsRowDiscount.InCheckBox_Checked;
colRowDiscount2.Visible := mpnIsRowDiscount.InCheckBox_Checked;
colRowDiscount3.Visible := mpnIsRowDiscount.InCheckBox_Checked;
end;

Jedná se o třídy TPDFDocument a TPDFFileAttachment, které umožňují práci s přílohami PDF dokumentů. Třídy jsou založeny na produktu třetí strany SecureBlackBox, který je součástí systému ABRA Gen, umí jak načíst existující přílohy, tak vložit nové:
procedure AddFileToPDF(const APDFFileName: string;
const AAddFileName: string; const AAddFileType: string; const AAddFileDescription: string);
var
i: Integer;
mPDFStream,
mPDFAttachmentStream: TFileStream;
mPDFDocument: TPDFDocument;
mPDFAttachment: TPDFFileAttachment;
begin
mPDFDocument := TPDFDocument.Create(nil);
try
// otevřeme stream s existujícím PDF souborem
mPDFStream := TFileStream.Create(APDFFileName, fmOpenReadWrite);
try
// pomocí streamu načteme PDF do objektu pro práci s PDF
mPDFDocument.Open(mPDFStream);
// do PDF přidáme přílohu
i := mPDFDocument.AddAttachedFile;
mPDFAttachment := mPDFDocument.AttachedFiles[i];
// vložíme novou přílohu načtením z disku
mPDFAttachmentStream := TFileStream.Create(AAddFileName, fmOpenRead);
try
// načteme obsah přílohy ze souboru
mPDFAttachment.LoadFromStream(mPDFAttachmentStream);
finally
mPDFAttachmentStream.Free;
end;
// doplníme další informace k příloze
mPDFAttachment.ObjectName := ExtractFileName(AAddFileName);
mPDFAttachment.FileName := ExtractFileName(AAddFileName);
mPDFAttachment.UnicodeFilename := ExtractFileName(AAddFileName);
mPDFAttachment.SubType := AAddFileType;
mPDFAttachment.Description := AAddFileDescription;
mPDFAttachment.CreationDate := Now;
mPDFAttachment.ModificationDate := Now;
// uložíme změny do souboru PDF
mPDFDocument.Close(True);
finally
mPDFStream.Free;
end;
finally
mPDFDocument.Free;
end;
end;
procedure ExtractFilesFromPDF(const APDFFileName: string;
const AExtractPath: string);
var
i: Integer;
mPDFStream,
mPDFAttachmentStream: TFileStream;
mPDFDocument: TPDFDocument;
mPDFAttachment: TPDFFileAttachment;
mFilesInformation: TStringList;
begin
mFilesInformation := TStringList.Create;
try
// vytvoříme objekt pro práci s PDF
mPDFDocument := TPDFDocument.Create(nil);
try
// otevřeme stream s existujícím PDF souborem
mPDFStream := TFileStream.Create(APDFFileName, fmOpenReadWrite);
try
// pomocí streamu načteme PDF do objektu pro práci s PDF
mPDFDocument.Open(mPDFStream);
// procházíme všechny přílohy vložené do PDF a uložíme je na disk
for i := 0 to mPDFDocument.AttachedFileCount - 1 do
begin
mPDFAttachment := mPDFDocument.AttachedFiles[i];
// do Stringlistu si uložíme případné informace o souborech
mFilesInformation.Add('Informace o příloze č. ' + IntToStr(i + 1) + ':' + nxCrLf +
' ObjectName: ' + mPDFAttachment.ObjectName + nxCrLf +
' FileName: ' + mPDFAttachment.FileName + nxCrLf +
' UnicodeFilename: ' + mPDFAttachment.UnicodeFilename + nxCrLf +
' SubType: ' + mPDFAttachment.SubType + nxCrLf +
' Description: ' + mPDFAttachment.Description + nxCrLf +
' CreationDate: ' + DateTimeToStr(mPDFAttachment.CreationDate) + nxCrLf +
' ModificationDate: ' + DateTimeToStr(mPDFAttachment.ModificationDate) + nxCrLf +
' Size: ' + IntToStr(mPDFAttachment.Size) + nxCrLf +
'================================================'+ nxCrLf + nxCrLf
);
mPDFAttachmentStream := TFileStream.Create(AExtractPath + mPDFAttachment.UnicodeFilename, fmCreate);
try
// přílohu z PDF uložíme přes stream na disk
mPDFAttachment.SaveToStream(mPDFAttachmentStream);
finally
mPDFAttachmentStream.Free;
end;
end;
mPDFDocument.Close(False);
//Uložíme informace o extrahovaných souborech do stejného adresáře kam se soubory extrahovali.
mFilesInformation.SaveToFile(AExtractPath + '_AttahcmentInfo.txt');
finally
mPDFStream.Free;
end;
finally
mPDFDocument.Free;
end;
finally
mFilesInformation.Free;
end;
end;

V tomto příkladu potřebujeme přenést zakázku a projekt z výrobního příkazu do CRM aktivity. V agendách existuje akce Aktivity - Založit novou aktivitu a připojit, standardně ale nemáme ve skriptingových háčcích při otevření aktivity přes tuto akci k dispozici zdrojový business objekt (zde výrobní příkaz). Tento příklad ukáže, jak se dá potřebný BO získat a použít v nějakém háčku (zde AfterSiteOpen_Hook).
V _InitSelectionParams_Hook si zapamatujeme objekt zdrojového dokladu a z něj pak přebíráme údaje v AfterSiteOpen_Hook.
Zdrojový kód skriptu - Agenda Aktivity:
var
fJO: TNxCustomBusinessObject;
procedure _InitSelectionParams_Hook(Self: TDynSiteForm; ASelection, AParams: TNxParameters);
var
mPar: TNxParameter;
begin
mPar := AParams.ParamByName('DocumentToConnect');
if assigned(mPar) then
begin
fJO := TNxCustomBusinessObject(mPar.asobject);
if fJO.CLSID <> Class_PLMJobOrder then //v této ukázce přebíráme jen z výrobních příkazů
begin
fJO := nil;
end;
end;
end;
procedure AfterSiteOpen_Hook(Self: TSiteForm);
var
mActivity: TNxCustomBusinessObject;
begin
mActivity := TDynSiteForm(Self).ActiveDataSet.CurrentObject;
try
if assigned(fJO) and assigned(mActivity) then //aktivita vytvořena z výrobního příkazu
begin
try
//tady údaje převezmeme
mActivity.SetFieldValueAsString('BusOrder_ID', fJO.GetFieldValueAsString('BusOrder_ID'));
mActivity.SetFieldValueAsString('BusProject_ID', fJO.GetFieldValueAsString('BusProject_ID'));
finally
fJO := nil;
end;
TDynSiteForm(Self).ActiveDataSet.UpdateFields;
end;
finally
mActivity.Free;
end;
end;
begin
end.

Zde je příklad, jak ze skriptingu vytvořit a odeslat e-mail v aplikaci Outlook. Pomocí skriptování můžeme vytvořit jakýkoliv zaregistrovaný objekt ve Windows, tedy i například Outlook.Application. Používá se k tomu metoda CreateOleObject.
Jaké vlastnosti, události nebo metody objekt používá, je možné najít v dokumentaci: Application object (Outlook) | Microsoft Learn.
procedure exeOutlookSend(Sender: TBasicAction);
var
mOutlook, mItem: Variant;
begin
mOutlook := CreateOleObject('Outlook.Application');
if VarIsNull(mOutlook) then NxShowSimpleMessage('Chyba při získání instance Outlooku', Sender.Site)
else begin
mItem := mOutlook.CreateItem(0);
mItem.To := 'adresa1@firma.cz'; //adresa příjemce
mItem.CC := 'adresa2@firma.cz; adresa3@firma.cz'; //adresy příjemců kopie
mItem.Subject := 'Předmět e-mailu';
mItem.Body := 'Tělo e-mailu';
mItem.Attachments.Add('C:\Users\jmeno\priloha.pdf'); //připojení souboru
//zobrazení okna Outlooku s vyvořeným e-mailem
mItem.Display; //případně .Send pro odeslání
end;
mOutlook := Null;
end;

Při přihlášení se volá háček Aplikační modul: Systémové události - AfterLogon_Hook. Tento háček se však nevolá pouze při prvotním přihlášení uživatele do AbraGen.exe, ale při každém vytvoření kontextu z klienta na aplikační server. K vytváření více kontextů na aplikační server (z jedné aplikace AbraGen.exe ) dochází od verze 23.2, a to při vyhodnocení údajů definovatelných panelů, které nyní probíhá v samostatném vlákně.
Existence více vláken má vliv i na použití globální proměnné GlobParams, parametry v ní lze použít jen v rámci jednoho vlákna. Hodnotu parametru nastavenou v jednom vlákně tak nelze získat v jiném vlákně. Aby bylo možné sdílet hodnoty parametrů mezi různými vlákny, byla vytvořena nová proměnná GlobThreadParams.
Následující příklad demonstruje, jak zabezpečit, aby se kód v háčku AfterLogon_Hook zavolal pouze jednou (při přihlášení uživatele do ABRA Gen), a jak hodnotu parametru nastavenou v jednom vlákně (při přihlášení uživatele do ABRA Gen) získat v jiném vlákně (při vyhodnocení definovatelného panelu).
Zároveň je třeba mít na paměti, že v háčku AfterLogon_Hook lze přistoupit ke GUI (například zobrazit formulář) pouze, pokud je kód vyvolán z hlavního vlákna. Pokud k přístupu ke GUI dojde z jiného vlákna celá aplikace může zamrznout.
const
cParSelectedDivisionID = 'SelectedDivisionID'; //Název parametru jehož hodnota se ukládá do GlobThreadParams
//Vyvolává se při tvorbě kontextu z klienta na aplikační server (nejen při přihlášení uživatele).
procedure AfterLogon_Hook(AContext: TNxContext);
begin
if NxIsMainThread and //Kód je volán z hlavního vlákna, ve kterém je možné zobrazit formulář
(GetSelectedDivisionID = '') and //Středisko ještě nebylo vybráno
Application.NxIsInteractive then //Jedná se o GUI aplikaci, která umožňuje zobrazení oken
begin
SetSelectedDivisionID(SelectDivisionID(AContext));
end;
end;
//Vyvolá výběr hodnoty z číselníku středisek.
function SelectDivisionID(AContext: TNxContext): String;
var
mRollParams: TNxParameters;
begin
mRollParams := TNxParameters.Create;
try
mRollParams.GetOrCreateParam(dtString, '_ID').AsString := ''; // Otevření na konkrétním záznamu. '' - bez určení
if NxShowRoll(AContext, Roll_Divisions, mRollParams, 0, '', nil) then
begin
Result := mRollParams.ParamByName('_ID').AsString;
end else
Result := SelectDivisionID(AContext); //Abychom donutili uživatele nějaké středisko vybrat
finally
mRollParams.Free;
end;
end;
//Uloží hodnotu do parametru SelectedDivisionID.
procedure SetSelectedDivisionID(AValue: String);
var
mParameters: TNxParameters;
mParameter: TNxParameter;
begin
mParameters := GlobThreadParams.LockParams;
try
mParameter := mParameters.GetOrCreateParam(dtString, cParSelectedDivisionID);
mParameter.AsString := AValue;
finally
GlobThreadParams.UnLockParams;
end;
end;
//Zjistí hodnotu parametru SelectedDivisionID.
//Tato metoda vrátí správně hodnotu parametru bez ohledu na to, z kterého vlákna je vyvolána.
//Například jí tedy lze použít v definovatelném panelu.
function GetSelectedDivisionID: String;
var
mParameters: TNxParameters;
begin
mParameters := GlobThreadParams.LockParams;
try
Result := mParameters.ParamAsString(cParSelectedDivisionID, '');
finally
GlobThreadParams.UnLockParams;
end;
end;

Příklad použití funkce GetUserParameters pro získání uživatelských parametrů na business objektu skladu, změna hodnoty vybraného parametru a uvolnění cache pro uživatelské parametry pro daný sklad pomocí ClearUserParametersCache:
Příklad použití:
procedure OnExecute_ChangeUserParamValue(Sender: TObject);
var
mSite: TSiteForm;
mStore: TNxCustomBusinessObject;
mUserParamValue: TNxCustomBusinessObject;
mUserParams: TNxParameters;
begin
mSite := TComponent(Sender).BusRollSite;
mStore := mSite.BaseObjectSpace.CreateObject(Class_Store);
try
mUserParamValue := mSite.BaseObjectSpace.CreateObject(Class_UserParamValue);
try
mUserParams := TNxParameters.Create;
try
// načtení BO, v tomto případě skladu
mStore.Load('3200000101');
// získání jeho uživatelských parametrů
mStore.GetUserParameters(mUserParams);
// vypsání hodnoty parametru s kódem "param01" ze skupiny parametrů s kódem "ParamGroup01"
ShowMessage(mUserParams.AsList.ParamByName('ParamGroup01').AsList.ParamByName('UserParameters').AsList.ParamByName('param01').AsList.ParamByName('ParamValue').AsString);
// načtení BO hodnoty parametru
mUserParamValue.Load(mUserParams.AsList.ParamByName('ParamGroup01').AsList.ParamByName('UserParameters').AsList.ParamByName('param01').AsList.ParamByName('ParamValue_ID').AsString);
// změna hodnoty parametru
mUserParamValue.SetFieldValueAsString('ParamValue', 'Nová hodnota');
mUserParamValue.Save;
// vyprázdnění keše parametrů pro daný BO
mStore.ClearUserParametersCache;
// vypsání změněné hodnoty
mStore.GetUserParameters(mUserParams);
ShowMessage(mUserParams.AsList.ParamByName('ParamGroup01').AsList.ParamByName('UserParameters').AsList.ParamByName('param01').AsList.ParamByName('ParamValue').AsString);
finally
mUserParams.Free;
end;
finally
mUserParamValue.Free;
end;
finally
mStore.Free;
end;
end;
..

Příklad tvorby JSON objektu s poli:
mJSON := TJSONSuperObject.Create;
try
mJSON.I['cislo_cele'] := 12345;
mJSON.D['desetinne_cislo'] := 8282.12;
mJSON.S['retezec'] := 'příliš žluťoučký koníček';
mJSON.O['subjson'] := mJSON.CreateJSON;
mJSON.O['subjson'].I['cislo_cele'] := 12345;
mJSON.O['subjson'].D['desetinne_cislo'] := 8282.12;
mJSON.O['subjson'].S['retezec'] := 'příliš žluťoučký koníček';
mJSON.O['pole'] := mJSON.CreateJSONArray;
for i := 0 to 3 do begin
mJSON.A['pole'].I[i] := i;
mJSON.A['pole'].S[i + 3] := IntToStr(i);
end;
mJSON.O['jinepole'] := mJSON.CreateJSONArray;
mJSONArray := mJSON.O['jinepole'].AsArray;
for i := 0 to 4 do begin
mItem := mJSON.CreateJSON;
mItem.S['name'] := 'Str' + IntToStr(i);
mJSONArray.Add(mItem);
end;
mJSON.O['dalsiprikladpole'] := mJSON.CreateJSONArray;
mJSONArray := mJSON.O['dalsiprikladpole'].AsArray;
for i := 0 to 2 do begin
mSubArray := mJSON.CreateJSONArray;
for ii := 0 to i do begin
mItem := mJSON.CreateJSON;
mItem.S['Str' + IntToStr(ii)] := IntToStr(i) + ' ' + IntToStr(ii);
mItem.I['Int' + IntToStr(ii)] := ii;
mSubArray.AsArray.Add(mItem)
end;
mJSONArray.Add(mSubArray);
end;
ShowMessage(mJSON.AsJson);
finally
mJSON.Free;
end;
Skript založí následující JSON:
{
"desetinne_cislo": 8282.12,
"retezec": "příliš žluťoučký koníček",
"subjson": {
"desetinne_cislo": 8282.12,
"retezec": "příliš žluťoučký koníček",
"cislo_cele": 12345
},
"dalsiprikladpole": [
[
{
"Str0": "0 0",
"Int0": 0
}
],
[
{
"Str0": "1 0",
"Int0": 0
},
{
"Str1": "1 1",
"Int1": 1
}
],
[
{
"Str0": "2 0",
"Int0": 0
},
{
"Str1": "2 1",
"Int1": 1
},
{
"Str2": "2 2",
"Int2": 2
}
]
],
"pole": [
0,
1,
2,
3,
"1",
"2",
"3"
],
"jinepole": [
{
"name": "Str0"
},
{
"name": "Str1"
},
{
"name": "Str2"
},
{
"name": "Str3"
},
{
"name": "Str4"
}
],
"cislo_cele": 12345
}

Příklad spuštění exportu Účetní exporty - Export předvahy do MS Excel, který vytváří HTML obsah, který je uložen jako soubor s přílohou XLS. Takovýto soubor umí následně otevřít MS Excel a lze pracovat s hodnotami.
Příklad použití:
// Uložení exportu do souboru
procedure GenerateEpxportTrialBalance(const AOS: TNxCustomObjectSpace; const AFrom, ATo: TDateTime; const APath: string);
const
cExportTrialBalanceID = 'P300000001'; // Identifikátor exportu Účetní exporty - Export předvahy do MS-Excel
var
mFile, mDynSourceID: string;
mContext: TNxContext;
mParameter, mCondParameter, mValuesParameter: TNxParameter;
mConditions: TNxParameters;
begin
mFile := APath + '\Předvaha_' + FormatDateTime('YYYYMMDD', AFrom) + '_' + FormatDateTime('YYYYMMDD', ATo) + '.xls';
if FileExists(mFile) then
DeleteFile(mFile);
mDynSourceID := AOS.SQLSelectFirstAsString('SELECT DataSource FROM Exports WHERE ID =''' + cExportTrialBalanceID + '''');
mContext := NxCreateContext(AOS);
try
mConditions := TNxParameters.Create;
try
// Nastavení data od do podmínky Datum účtování
mParameter := mConditions.GetOrCreateParam(dtList, 'AccDate').AsList;
mParameter.AsList.NewFromDataType(dtInteger, 'USEDKIND', pkUnknown).AsInteger := 2; //ckRange;
mValuesParameter := mParameter.AsList.NewFromDataType(dtList, 'VALUES', pkUnknown);
mValuesParameter.AsList.NewFromDataType(dtFloat,'{:VALUE}', pkUnknown).AsFloat := AFrom;
mValuesParameter.AsList.NewFromDataType(dtFloat,'{:VALUEHIGH}', pkUnknown).AsFloat := ATo;
CFxReportManager.ExportByConditions(mContext, mConditions, mDynSourceID, cExportTrialBalanceID, 0, '', mFile);
finally
mConditions.Free;
end;
finally
mContext.Free;
end;
end;
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction, mAction2: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Spuštění exportu předvahy';
mAction.Category := 'tabList';
mAction.OnExecute := @RunGenerateEpxportTrialBalance;
end;
procedure RunGenerateEpxportTrialBalance(Sender : TObject);
var
mSite: TSiteForm;
mObjectSpace: TNxCustomObjectSpace;
begin
mSite := TComponent(Sender).Site;
mObjectSpace := mSite.BaseObjectSpace;
GenerateEpxportTrialBalance(mObjectSpace, StrToDate('1.1.2000'), StrToDate('1.1.2025'), 'c:\temp')
end;

Ve skriptování je možné využít snadného zápisu do logu, který se konfiguruje za pomocí standardního nastavení přes nexus.cfg. Nastavením lze snadno určit do jaké míry bude logování ze skriptování detailní pomocí určení hodnoty úrovně (Level).
Pro zapnutí logování (nastavení Enabled=1) není třeba restartovat klienty ani aplikační server. Nastavení si všechny aplikace jednou za cca 60 sekund obnovují.
Příklad: Obsah Nexus.cfg
[Logs]
LogsDirectory=\\Server\složka_pro_zápis
[Log.Scripting]
Enabled=1
# Pro zápis chyb do logu nastavíme Level=2, pro zápis varování hodnotu Level=3 a pro detailní výpis Level=5
Level=5
Uvedená cesta LogsDirectory je třeba aby směřovala na síťové úložiště, kam mají klienti přístup zápisu.
V případě chybné cesty může docházet k velkému výkonovému zpomalení celého systému.
Příklad skriptu:
procedure ExampleUseNxScriptingLog;
begin
if NxScriptingLog.Active then begin
// Úrovně závažnosti jsou definovány výčtem TNxLogLevel = (logSystem,logCritical,logError,logWarning,logNotice,logInfo,logDebug)
// Nejvyšší úroveň závažnosti logSystem hodnota 0, ve skritpování nepoužíváme.
// NxScriptingLog.WriteEvent(logError, 'Text systémové chyby');
// Úroveň vznikla chyba Level=2
NxScriptingLog.WriteEvent(logError, 'Text vznikla chyby');
// Úroveň varování Level=3
NxScriptingLog.WriteEvent(logWarning, 'Text varování');
// Úroveň detailní výpis informací Level=5
NxScriptingLog.WriteEvent(logInfo, 'Informativní text');
NxScriptingLog.EnterSection('Logování bloku');
try
try
Sleep(1000);
RaiseException('Odchycena očekávaná výjimka');
except
NxScriptingLog.WriteEvent(logWarning, ExceptionMessage);
end;
finally
NxScriptingLog.LeaveSection('Logování bloku');
end;
end;
end;
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Zalogování textu do textového logu';
mAction.Category := 'tabList';
mAction.OnExecute := @RunWriteToLog;
end;
procedure RunWriteToLog(Sender: TObject);
begin
ExampleUseNxScriptingLog;
end;
Výsledkem je zápis do souboru s názvem, který obsahuje datum a čas vzniku souboru, název aplikace, proces, identifikátor aplikace a jméno počítače.
Příklad jména vzniklého logu: 24-03-26 16-32-17-946 AbraGen 16556 JAVU-SRV.JAVU-NTB ABRAGen.log
Obsah souboru z uvedeného příkladu:
26.03.2024 22:03:43.483 [2] 00001A68 (Scripting) Text vznikla chyby
26.03.2024 22:03:43.483 [3] 00001A68 (Scripting) Text varování
26.03.2024 22:03:43.483 [5] 00001A68 (Scripting) Informativní text
26.03.2024 22:03:43.483 [4] 00001A68 (Scripting) Logování bloku
26.03.2024 22:03:43.483 [4] 00001A68 (Scripting) ->
26.03.2024 22:03:44.501 [3] 00001A68 (Scripting) Error in ExampleUseNxScriptingLog (Exception):
očekávaná výjimka
scripting callstack:
ExampleUseNxScriptingLog (): .RaiseException (40:23)
RunWriteToLog (ExampleUseNxScriptingLog.Faktury přijaté (Agenda)): ExampleUseNxScriptingLog (16:27)
RunWriteToLog (14:1)
26.03.2024 22:03:44.501 [5] 00001A68 (Scripting)<-- (Logování bloku)

Příklad demonstruje vytvoření XML dokumentu s digitálním podpisem a jeho vložení do SOAP obálky za účelem komunikace se SÚKL (Státní ústav pro kontrolu léčiv):
procedure SignXmlSukl(Sender: TComponent);
var
mSite: TSiteForm;
mOS: TNxCustomObjectSpace;
mXML, mXMLEnvelope: TNxScriptingXMLWrapper;
mCertStore, mCertHash, mMsgGUID: String;
mContext: TNxContext;
begin
mSite:= Sender.Site;
mOS:= mSite.BaseObjectSpace;
mContext:= NxCreateContext(mOS);
try
//vyvoláme dialog s výběrem podpisového certifikátu a uložíme si jeho hash a místo uložení
mCertHash:= SelectCertificateDlg(mContext, mCertStore, mSite);
//vygenerujeme si GUID odesílané zprávy
mMsgGUID:= NxTrim(LowerCase(GUIDToString(CFxGuid.CreateNew())),'{}');
mXML:= TNxScriptingXMLWrapper.Create;
try
//vytvoříme zprávu k podepsání
mXML.DateTimeFormat:= 'yyyy-mm-dd"T"hh:nn:ss.zzz';
mXML.CreateEmpty('com:AppPingZEPDotaz', 'xmlns:com="http://www.sukl.cz/erp/common"');
mXML.setAttributeValue('com:AppPingZEPDotaz', 'xmlns:com', 'http://www.sukl.cz/erp/common');
mXML.setElementAsString('com:Doklad.com:Pristupujici.com:Uzivatel', cUserLogin);
mXML.setElementAsString('com:Doklad.com:Pristupujici.com:Pracoviste', cPremiseCode);
mXML.setElementAsString('com:Zprava.com:ID_Zpravy', mMsgGUID);
mXML.setElementAsString('com:Zprava.com:Verze', cSUKLInterfaceVersion);
mXML.setElementAsDateTime('com:Zprava.com:Odeslano', Now);
mXML.setElementAsString('com:Zprava.com:SW_Klienta', 'ABRASW');
//uděláme XML kanonickým, a podepíšeme (Kanonozizace upraví xml do konzistentní standardizované formy)
mXML.MakeXMLCannonical(0, false);
mXML.SignXML(mCertHash, mCertStore, 1, mContext, 'ds');
//vytvoříme obálku
mXMLEnvelope:= TNxScriptingXMLWrapper.Create;
try
mXMLEnvelope.CreateEmpty('soapenv:Envelope', 'xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"');
mXMLEnvelope.setAttributeValue('soapenv:Envelope', 'xmlns:soapenv', 'http://schemas.xmlsoap.org/soap/envelope/');
mXMLEnvelope.setAttributeValue('soapenv:Envelope', 'xmlns:com', 'http://www.sukl.cz/erp/common');
mXMLEnvelope.setAttributeValue('soapenv:Envelope', 'xmlns:ds', 'http://www.w3.org/2000/09/xmldsig#');
mXMLEnvelope.addElement('soapenv:Header');
//podepsanou XML zprávu vložíme do obálky
mXMLEnvelope.AddXMLEncodedElement('soapenv:Body', mXML.getElementXML('com:AppPingZEPDotaz'));
mXMLEnvelope.saveToFile('F:\testXML1.xml', 'UTF-8');
finally
mXMLEnvelope.Free;
end;
finally
mXML.Free;
end;
finally
mContext.Free;
end;
end;

Při použití interního OLE se skriptingu dbejte na jeho uvolnění!
Nejen, že neuvolněné OLE způsobuje Memory Leaky, ale v kombinaci s přihlášením jiném uživatele může vést k situaci, kdy systém přestane fungovat neboť dojde k částečné likvidaci původního ObjectSpace, který již pro další použití není plně funkční !!!
Je třeba dbát na to, aby byly uvolněny i všechny další objekty, které so OLE mohou dále držet, například objekt vytvořený prostřednictvím metody CreateDocumentDriver.
try
mOLE := mContext.GetAbraOLEApplication();
mOleDoc := mOLE.CreateDocumentDriver();
...
finally
//Tyto řádky zajistí uvolnění OLE a jsou nezbytné pro další správnou funkci systému
mOleDoc := nil;
mOLE := nil;
end;

Práce s definovatelnými importy: Definovatelné importy (txt, xls a csv) vyžadují, aby v importním souboru byla rozlišena hlavička a řádky pomocí masky. Pokud tomu tak není, jsou jednotlivé řádky ze souboru importovány jako samostatné doklady.
Pro případ, kdy jsou v importních datech pouze řádkové položky více než jedné hlavičky a zároveň jde o řádky jen jednoho druhu business objektu, byly vytvořeny nové skritptingové funkce dostupné na třídě TNxIEImportDefinition.
Soubor typu TXT:
Funkce ConvertOnlyRowsTXTData, metoda ConvertOnlyRowsTXTData, procedure ConvertOnlyRowsTXTData(var ATXTInputData: TStringList; const AKeyPositions: TStringList; const AHeadersPrefix, ARowsPrefix: string; ATrimSpaces: Boolean)
{
Vyvolává se po nastavení importního dokumentu před spuštěním parsingu - umožňuje změnit obsah importního dokumentu.
}
procedure IEImportExport_AfterSetImportDocument_Hook(AContext: TNxContext; const AImportDefinition_BO: TNxCustomBusinessObject; var ADocumentContent: TStringList);
var
mKeyPositions: TStringlist;
begin
mKeyPositions := TStringlist.Create;
try
mKeyPositions.Add('1;12');
mKeyPositions.Add('13;12');
mKeyPositions.Add('27;11');
TNxIEImportDefinition(AImportDefinition_BO).ConvertOnlyRowsTXTData(ADocumentContent, mKeyPositions, 'HEAD ', 'ROW ', false);
{ TNxIEImportDefinition(AImportDefinition_BO).ConvertOnlyRowsTXTData(ADocumentContent, mKeyPositions, 'HEAD ', 'ROW ', true);
}
finally
mKeyPositions.Free;
end;
end;
begin
end.
Soubor typu CSV a XLS (soubor XLS se při importu převede na CSV formát):
Funkce ConvertOnlyRowsCSVData, metoda ConvertOnlyRowsCSVData, procedure ConvertOnlyRowsCSVData(var ACSVInputData: TStringList; const AKeyPositions: TStringList; const AHeadersPrefix, ARowsPrefix: string)
{
Vyvolává se po nastavení importního dokumentu před spuštěním parsingu - umožňuje změnit obsah importního dokumentu.
}
procedure IEImportExport_AfterSetImportDocument_Hook(AContext: TNxContext; const AImportDefinition_BO: TNxCustomBusinessObject; var ADocumentContent: TStringList);
var
mKeyPositions: TStringlist;
begin
mKeyPositions := TStringlist.Create;
try
mKeyPositions.Add('0');
mKeyPositions.Add('1');
mKeyPositions.Add('3');
TNxIEImportDefinition(AImportDefinition_BO).ConvertOnlyRowsCSVData(ADocumentContent, mKeyPositions, 'HEAD', 'ROW');
finally
mKeyPositions.Free;
end;
end;
begin
end.

Příklad použití:
Na skladové kartě potřebujeme zobrazit hodnotu prodeje za posledních 365 dnů. Nechceme však aby se tato hodnota načítala dynamicky (například do sloupce přes NxSQLSelect) ale, aby byla perzistentní. Dle dané položky lze i řadit seznam, případně hodnotu použít pro další výpočty.
Načtení bude probíhat naplánovanou úlohou typu skript. Skript zapíše pro všechny skladové karty prodej za posledních 365 dnů. Zápis probíhá sekvenčně aby nedošlo k uzamčení tabulky StoreCards.
Příklad nastavení a provedení:
Na objektu skladové karty vytvoříme definovatelnou položku X_Prodej365 typu Číslo.
Položku X_Prodej365 si zobrazíme do sloupce v agendě Skladové karty. Před prvním přepočtem je pochopitelně hodnota 0 u každé skladové karty – výchozí hodnota (z definice definovatelné položky).
V agendě Balíčky skriptů založíme skript pro výpočet prodejů za posledních 365 dnů.
Ve zdrojovém kódu je možné rychle vygenerovat hlavičku skriptu „Volání z naplánované úlohy“.
Success – určuje, jestli úloha proběhla v pořádku
LogInfoStr – naplánované úloze můžeme vrátit dodatečné informace o průběhu, například počet změněných záznamů, případně jiné informace.
Metodu pojmenujeme a dopíšeme obsah dle následujícího příkladu.
procedure Run( ObjectSpace: TNxCustomObjectSpace; var Success: Boolean; var LogInfoStr: String); const cStoreCards = ' SELECT SC.ID AS ID, SUM(RO2.DeliveredQuantity) AS DeliveredQuantity FROM ReceivedOrders2 RO2' + ' JOIN ReceivedOrders RO ON RO.ID = RO2.Parent_ID' + ' JOIN StoreCards SC ON RO2.StoreCard_ID = SC.ID' + ' WHERE' + ' SC.Hidden = ''N'' AND' + ' RO.DocDate$DATE >= :FromDate' + ' GROUP BY SC.ID'; cUpdateStoreCard = 'UPDATE StoreCards SET X_Prodej365 = :X_Prodej365 WHERE ID = :ID'; var mStoreCardsData: TMemoryDataset; I: Integer; mInputParams: TNxParameters; mSC: TNxCustomBusinessObject; mStoreCardID: String; mDeliveredQuantity: Extended; begin Success := True; LogInfoStr := ''; mStoreCardsData := TMemoryDataset.Create(nil); try mInputParams := TNxParameters.Create; try // Získání dat přes SQL dotaz mInputParams.Clear; mInputParams.NewFromDataType(dtFloat, 'FromDate').AsFloat := Date - 365; ObjectSpace.SQLSelect2(cStoreCards, mStoreCardsData, mInputParams); // Sekvenčně - modifikace položky X_Prodej365 // A. Změna přes SQL Update - přímá DB změna bez zápisu sledování změn - obchází business logiku // B. Změna přes business logiku - změny jsou viditelné v sledování změn pokud je nastaveno na třídě mStoreCardsData.First; while not mStoreCardsData.Eof do begin mStoreCardID := mStoreCardsData.Fields.FieldByName('ID').AsString; mDeliveredQuantity := mStoreCardsData.Fields.FieldByName('DeliveredQuantity').AsFloat; // A. Změna přes SQL Update mInputParams.Clear; mInputParams.NewFromDataType(dtString, 'ID').AsString := mStoreCardID; mInputParams.NewFromDataType(dtFloat, 'X_Prodej365').AsFloat := 666; ObjectSpace.SQLExecute(cUpdateStoreCard, mInputParams); // ---A. // nebo // B. Změna přes business logiku mSC := ObjectSpace.CreateObject(Class_StoreCard); try mSC.Load(mStoreCardID); mSC.SetFieldValueAsFloat('X_Prodej365', mDeliveredQuantity); mSC.Save; finally mSC.Free; end; // ---B. mStoreCardsData.Next; end; finally mInputParams.Free; end; finally mStoreCardsData.Free; end; end; begin end.
Skript nejdřív načte data prodejů za posledních 365 dnů a hodnoty uloží do MemoryDataset. Poté sekvenčně projde všechny záznamy datasetu a provede změnu definovatelné položky X_Prodej365. Tady máme na výběr z dvou možností:
A. Změna přes SQL Update - přímá DB změna bez zápisu sledování změn - obchází business logiku
B. Změna přes business logiku - změny hodnot jsou viditelné v sledování změn (pokud je nastaveno na dané třídě).
(v ukázkovém skriptu teda ponechat jenom jednu z možností)
Založíme novou naplánovanou úlohu typu Skript.
Provedení naplánované úlohy vykoná přepočet prodejů a hodnoty zapíše do položky X_Prodej365. V číselníku skladových karet po občerstvení již máme napočtené hodnoty prodejů za posledních 365 dnů. Dle položky X_Prodej365 lze seznam i řadit nebo třídit.

Pro agendu Skladové karty se v okně náhledů zobrazí záložka Prodej, kde bude na Google Charts grafech ukázán prodej
Je třeba mít vystavěné faktury se zbožím za posledních 14 a 30 dnů na různá střediska, aby skript zobrazoval data.
Druh skriptu je Aplikační modul - Systémové události.
{
Vyvolá se během načítání záložek pro náhled příloh.
}
procedure DocumentsViewer_AddTabs_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject; const ASiteCLSID: string; var AParams: TNxParameters);
var
mTabData: TNxParameters;
begin
if ASourceObject.CLSID = Class_StoreCard then
begin
mTabData := TNxParameters.Create;
mTabData.GetOrCreateParam(dtString, 'Name').AsString := 'Prodeje';
mTabData.GetOrCreateParam(dtString, 'ID').AsString := ASourceObject.OID;
AParams.AsList.Add(mTabData);
end;
end;
{
Vyvolá se při kliknutí na záložku bez dat vytvořenou skriptem.
}
procedure DocumentsViewer_AddContent_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject; const ASiteCLSID: string; AID: string; var AParams: TNxParameters);
var
mInputParams: TNxParameters;
mTableParams: TNxParameters;
mParams, mSteppedChartParams: TNxParameters;
mMemTable: TMemTable;
mChartPage: TGoogleChartsHtmPage;
mTableChart, mPieChart, mSteppedChart: string;
mTableColumns, mPieChartNames: array of string;
mPieChartValues: array of Double;
I: Integer;
begin
if (ASourceObject.CLSID = Class_StoreCard) then
begin
mInputParams := TNxParameters.Create;
mMemTable := TMemTable.Create(nil);
mTableParams := TNxParameters.Create;
mSteppedChartParams := TNxParameters.Create;
try
mInputParams.NewFromDataType(dtString, 'ID').AsString := ASourceObject.OID;
mInputParams.NewFromDataType(dtDate, 'DateFrom30').AsDateTime := Now - 30;
mInputParams.NewFromDataType(dtDate, 'DateFrom14').AsDateTime := Now - 14;
AContext.SQLSelect2(
'SELECT D.Code AS Code, '+
'(SELECT SUM(II2.QUANTITY) '+
'FROM ISSUEDINVOICES2 II2 '+
'LEFT JOIN ISSUEDINVOICES II ON II2.Parent_ID = II.ID '+
'WHERE '+
' II2.DIVISION_ID = D.ID AND '+
' II2.StoreCard_ID = :ID AND '+
' II.DOCDATE$DATE >= :DateFrom30 '+
') AS Q30, '+
'(SELECT SUM(II2.QUANTITY) '+
'FROM ISSUEDINVOICES2 II2 '+
'LEFT JOIN ISSUEDINVOICES II ON II2.Parent_ID = II.ID '+
' WHERE '+
' II2.DIVISION_ID = D.ID AND '+
' II2.StoreCard_ID = :ID AND '+
' II.DOCDATE$DATE >= :DateFrom14 '+
') AS Q14 '+
'FROM DIVISIONS D',
mMemTable, mInputParams);
SetLength(mPieChartNames, mMemTable.RecordCount);
SetLength(mPieChartValues, mMemTable.RecordCount);
mParams := mSteppedChartParams.NewFromDataType(dtList, '').AsList;
mParams.NewFromDataType(dtString, '').AsString := 'Středisko';
mParams.NewFromDataType(dtString, '').AsString := 'Q14';
mParams.NewFromDataType(dtString, '').AsString := 'Q30';
mMemTable.First;
while not mMemTable.Eof do
begin
mParams := mTableParams.NewFromDataType(dtList, IntToStr(mMemTable.RecNo)).AsList;
mParams.NewFromDataType(dtString, 'Středisko').AsString := mMemTable.FieldByName('Code').AsString;
mParams.NewFromDataType(dtFloat, 'Q14').AsFloat := mMemTable.FieldByName('Q14').AsFloat;
mParams.NewFromDataType(dtFloat, 'Q30').AsFloat := mMemTable.FieldByName('Q30').AsFloat;
mPieChartNames[mMemTable.RecNo - 1] := mMemTable.FieldByName('Code').AsString;
mPieChartValues[mMemTable.RecNo - 1] := mMemTable.FieldByName('Q30').AsFloat;
mParams := mSteppedChartParams.NewFromDataType(dtList, '').AsList;
mParams.NewFromDataType(dtString, '').AsString := mMemTable.FieldByName('Code').AsString;
mParams.NewFromDataType(dtFloat, '').AsFloat := mMemTable.FieldByName('Q14').AsFloat;
mParams.NewFromDataType(dtFloat, '').AsFloat := mMemTable.FieldByName('Q30').AsFloat;
mMemTable.Next;
end;
SetLength(mTableColumns, 3);
mTableColumns[0] := 'Středisko';
mTableColumns[1] := 'Q14';
mTableColumns[2] := 'Q30';
mTableChart := CFxGoogleCharts.RenderTableChart('tablechart_Sales', 'Sales', mTableColumns, mTableParams);
mPieChart := CFxGoogleCharts.RenderPieChart('piechart_Sales', 'Sales', 'Střediska', 'Počet prodaných kusů', mPieChartNames, mPieChartValues);
mSteppedChart := CFxGoogleCharts.RenderSteppedAreaChart('steppedchart_Sales', 'Sales', mSteppedChartParams);
mChartPage := TGoogleChartsHtmPage.Create;
try
mChartPage.AddChart(mTableChart);
mChartPage.AddChart(mPieChart);
mChartPage.AddChart(mSteppedChart);
AParams.NewFromDataType(dtString, 'Content').AsString := mChartPage.Render('Sales Chart');;
AParams.NewFromDataType(dtString, 'Format').AsString := 'HTML';
finally
mChartPage.Free;
end;
finally
mMemTable.Free;
mInputParams.Free;
mTableParams.Free;
mSteppedChartParams.Free;
end;
end;
end;
begin
end.

Následující skript načítá pro okno náhledů v agendě Servisované předměty z definovatelné extra položky s názvem Folder a datového typu Znaky cestu k adresáři (cestu zadáváme bez uvozovek), ve kterém můžeme mít uložené přílohy. Ty se zobrazí v samostatných záložkách okna náhledů. Pokud je navíc v této externí složce podsložka s názvem Fotodokumentace, ve které jsou uložené obrázky, vytvoří z těchto obrázků skript náhledovou HTML galerii, která je v okně náhledů zobrazena jako jedna samostatná záložka.
Výsledek v agendě Servisované předměty
Druh skriptu je Aplikační modul - Systémové události.
procedure DocumentsViewer_AddTabs_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject; const ASiteCLSID: string; var AParams: TNxParameters);
var
mPath: string;
mData: TmemoryDataSet;
procedure FillParametersFromUNCFiles;
var
mList: TStringList;
i: integer;
mTabData: TNxParameters;
mName: string;
mPathName: string;
begin
mList := TStringList.Create();
try
NxGetFileList(mPath, mList, '*.*', false);
if mList.Count > 0 then
begin
for i := 0 to mList.Count - 1 do
begin
mName := extractFileName(mList[i]);
mPathName := mPath + '\' + mList[i];
if (not DirectoryExists(mPathName)) then
begin
mTabData := TNxParameters.Create;
mTabData.GetOrCreateParam(dtString, 'Name').AsString := mName;
mTabData.GetOrCreateParam(dtString, 'ID').AsString := mPath + IntToStr(i);
mTabData.GetOrCreateParam(dtString, 'Path').AsString := mPathName;
AParams.AsList.Add(mTabData);
end;
end;
end;
finally
mList.Free;
end;
end;
procedure FillGaleryTabFromUNCFiles(aName: String);
var
mTabData: TNxParameters;
begin
mTabData := TNxParameters.Create;
mTabData.GetOrCreateParam(dtString, 'Name').AsString := aName;
mTabData.GetOrCreateParam(dtString, 'ID').AsString := mPath;
AParams.AsList.Add(mTabData);
end;
begin
case ASourceObject.CLSID of
Class_Storecard:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
end;
Class_BusOrder:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
end;
Class_PLMProduceRequest, Class_PLMJobOrder:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'Storecard_ID.X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'BusOrder_ID.X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
end;
Class_ServiceDocument:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'ServicedObject_ID.X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'BusOrder_ID.X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'ServicedObject_ID.X_Folder', '') + '\Fotodokumentace';
if DirectoryExists(mPath) and (mPath <> '') then FillGaleryTabFromUNCFiles('Fotodokumentace');
end;
Class_ServicedObject:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '') + '\Fotodokumentace';
if DirectoryExists(mPath) and (mPath <> '') then FillGaleryTabFromUNCFiles('Fotodokumentace');
end;
Class_ServicedObjectType:
begin
try
mData := TMemoryDataSet.Create(nil);
ASourceObject.ObjectSpace.SQLSElect2('select x_folder,x_sn from servicedobjects where servicedobjecttype_id = ' + QuotedStr(ASourceObject.OID), mData);
if mData.Active then
begin
mData.First;
while not mData.Eof do
begin
mPath := mData.FieldByName('X_Folder').AsString + '\Fotodokumentace';
if DirectoryExists(mPath) and (mPath <> '') then FillGaleryTabFromUNCFiles(mData.FieldByName('x_sn').AsString);
mData.Next;
end;
end;
finally
mData.Free;
end;
end;
end;
end;
procedure DocumentsViewer_AddContent_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject; const ASiteCLSID: string; AID: string; var AParams: TNxParameters);
var
mPath: string;
procedure FillDataSetFromUNCPictures;
var
mList, mHTMLPict: TStringList;
i: integer;
mFileNamepict: string;
mContent: TNxParameters;
begin
mList := TStringList.Create();
mHTMLPict := TStringList.Create();
try
NxGetFileList(mPath, mList, '*.jpg', false);
if mList.Count > 0 then
for i := 0 to mList.Count - 1 do
if Fileexists(mPath + '\' + mList[i]) then
mHTMLPict.Add('<strong>' + mList[i] + '</strong><br /><a href="' + mPath + '\' + mList[i] + '" target="_blank"><img src="' + mPath + '\' + mList[i] + '" width="350" /></a><br />');
NxGetFileList(mPath, mList, '*.jpeg', false);
if mList.Count > 0 then
for i := 0 to mList.Count - 1 do
if Fileexists(mPath + '\' + mList[i]) then
mHTMLPict.Add('<strong>' + mList[i] + '</strong><br /><a href="' + mPath + '\' + mList[i] + '" target="_blank"><img src="' + mPath + '\' + mList[i] + '" width="350" /></a><br />');
NxGetFileList(mPath, mList, '*.png', false);
if mList.Count > 0 then
for i := 0 to mList.Count - 1 do
if Fileexists(mPath + '\' + mList[i]) then
mHTMLPict.Add('<strong>' + mList[i] + '</strong><br /><a href="' + mPath + '\' + mList[i] + '" target="_blank"><img src="' + mPath + '\' + mList[i] + '" width="350" /></a><br />');
NxGetFileList(mPath, mList, '*.bmp', false);
if mList.Count > 0 then
for i := 0 to mList.Count - 1 do
if Fileexists(mPath + '\' + mList[i]) then
mHTMLPict.Add('<strong>' + mList[i] + '</strong><br /><a href="' + mPath + '\' + mList[i] + '" target="_blank"><img src="' + mPath + '\' + mList[i] + '" width="350" /></a><br />');
if mHTMLPict.Count > 0 then
begin
AParams.NewFromDataType(dtString, 'Content').AsString := mHTMLPict.Text;
AParams.NewFromDataType(dtString, 'Format').AsString := 'HTML';
end;
finally
mList.Free;
mHTMLPict.Free;
end;
end;
begin
mPath := AID;
case ASourceObject.CLSID of
Class_ServiceDocument:
begin
if DirectoryExists(mPath) and (mPath <> '') then FillDataSetFromUNCPictures;
end;
Class_ServicedObject:
begin
if DirectoryExists(mPath) and (mPath <> '') then FillDataSetFromUNCPictures;
end;
Class_ServicedObjectType:
begin
if DirectoryExists(mPath) and (mPath <> '') then FillDataSetFromUNCPictures;
end;
end;
end;
begin
end.

Následující příklad ukazuje, jak získat přístupový token potřebný pro autentizaci při komunikaci s novým Firebase Cloud Messaging API (V1) pomocí protokolu OAuth 2.0. Token je generován na základě privátního klíče servisního účtu Google a obsahuje oprávnění specifická pro službu Firebase Messaging.
procedure GetAccessToken(Sender: TBasicAction);
const
GOOGLE_AUTH_JSON = '{' +
'"type": "service_account",' +
'"project_id": "**********",' +
'"private_key_id": "**************************************",' +
'"private_key": "-----BEGIN PRIVATE KEY-----\*******************************' +
'****************************\n-----END PRIVATE KEY-----\n",' +
'"client_email": "firebase-adminsdk-*****@********.iam.gserviceaccount.com",' +
'"client_id": "*******************",' +
'"auth_uri": "https://accounts.google.com/o/oauth2/auth",' +
'"token_uri": "https://oauth2.googleapis.com/token",' +
'"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",' +
'"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-************.iam.gserviceaccount.com",' +
'"universe_domain": "googleapis.com"' +
'}';
begin
// získáme access token ke službě "firebase.messaging" přes OAuth 2.0 na základě údajů Google servisního účtu
ShowMessage(CFxInternet.GetGoogleOAuth2AccessToken(GOOGLE_AUTH_JSON, 'https://www.googleapis.com/auth/firebase.messaging', jwtaRS256, 60));
end;

procedure InitSite_Hook(Self: TSiteForm);
var
mAction: TMultiAction;
begin
mAction := Self.GetNewMultiAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Dohledat ve fakturách';
mAction.Items.Add('Dohledat ve fakturách vydaných (script)');
mAction.Category := 'tabList';
mAction.OnExecuteItem := @Test;
end;
procedure Test(Sender: TObject; AIndex :Integer);
var
mSite: TSiteForm;
mParams, mDefaultSelection: TNxParameters;
mParCondition: TNxParameter;
mTmpList: TStringList;
mTmpPar: TNxParameter;
mValues: TNxParameters;
begin
mSite := TComponent(Sender).Site;
mParams := TNxParameters.Create;
try
mParams.NewFromDataType(dtString, '_SelectionCaption').AsString := 'Otevřeno ze skriptování řada FV nezaplacené pro firmy A%';
mDefaultSelection := mParams.NewFromDataType(dtList, '_DefaultSelection').AsList;
mParCondition := mDefaultSelection.AsList.NewFromDataType(dtList, 'CONDITIONS');
mTmpPar := mParCondition.AsList.NewFromDataType(dtList, 'DocDate');
mTmpPar.AsList.NewFromDataType(dtInteger, 'USEDKIND').AsInteger := ckRange;
mValues := mTmpPar.AsList.NewFromDataType(dtList, 'VALUES').AsList;
mValues.NewFromDataType(dtString, '{:LOW}').AsString := '0';
mValues.NewFromDataType(dtString, '{:HIGH}').AsString := '45659';
mTmpPar := mParCondition.AsList.NewFromDataType(dtList, 'PaidStatus');
mTmpPar.AsList.NewFromDataType(dtInteger, 'USEDKIND').AsInteger := ckSingle;
mValues := mTmpPar.AsList.NewFromDataType(dtList, 'VALUES').AsList;
mValues.NewFromDataType(dtString, '{:VALUE}').AsString := '1;2;';
mTmpPar := mParCondition.AsList.NewFromDataType(dtList, 'UserDynSQLCondition');
mTmpPar.AsList.NewFromDataType(dtInteger, 'USEDKIND').AsInteger := ckSingle;
mValues := mTmpPar.AsList.NewFromDataType(dtList, 'VALUEBAG').AsList;
mValues.NewFromDataType(dtString, 'DYNUSERSQL').AsString := '(SELECT Name FROM Firms UserSQLFirm WHERE UserSQLFirm.ID = A.Firm_ID) LIKE ''A%''';
mTmpPar := mParCondition.AsList.NewFromDataType(dtList, 'DocQueue_ID');
mTmpPar.AsList.NewFromDataType(dtInteger, 'USEDKIND').AsInteger := ckList;
mTmpPar.AsList.NewFromDataType(dtString, 'VALUELIST').AsString := '''5600000101''';
mSite.ShowDynForm(Site_IssuedInvoices, mParams, nil, True, '');
finally
mParams.Free;
end;
end;
begin
end.

Od verze 25.0.93 lze využít tento poměrně rozšířený a bezpečný standard přímo z prostředí skriptování pomocí třídy TOAuth2Wizard. Díky tomu jsme schopni využívat služeb poskytovatelů, kteří podporují ověření uživatele prostřednictvím třetí strany, případně sami používají vlastní identity server.
Příklad napojení na službu Everifin, která poskytuje služby otevřeného bankovnictví s licencí PSD2:
procedure UserLogin(aSite: TSiteForm);
var
mOauth: TOAuth2Wizard;
begin
mOauth := TOAuth2Wizard.Create(aSite.SiteContext);
try
mOauth.ClientSecret := 'XXXXX';
mOauth.ClientId := 'abra-test';
mOauth.Scope := 'ais';
mOauth.AuthorizationUrl := 'https://api.everifin.com/auth/realms/everifin_app/protocol/openid-connect/auth?...';
mOauth.OnResponse := @OAuth2Wizard_Response;
mOauth.SkipLoginPage := True;
mOauth.Execute(aSite.FindParentForm);
finally
mOauth.Free;
end;
end;
procedure OAuth2Wizard_Response(Sender: TObject; aRequestParams: TStrings; var aState: TNxOAuth2ResultAuthorizationStatus; var aMessage: string);
var
mCode: String;
mOS: TNxCustomObjectSpace;
mToken: String;
mTokenEncrypted: String;
begin
aState := noasNone;
aMessage := '';
try
mCode := aRequestParams.Values('code');
if NxIsBlank(mCode) then begin
RaiseException('Parameter ''code'' se nenašel.');
end;
if not (Sender is TOAuth2Wizard) then begin
RaiseException('Incompatible sender type.');
end;
mOS := TOAuth2Wizard(Sender).ObjectSpace;
mToken := ObtainAccessToken(mOS, mCode); // Získání přístupového tokenu přes REST API
mTokenEncrypted := CFxCrypt.EncryptWithANSIKeyToBase64(cCryptoSecretKey, TEncoding.UTF8.GetBytes(mToken)); // Zašifrování tokenu
// Zde může přijít uchování zašifrovaného tokenu.
aState := noasOK;
except
aState := noasError;
aMessage := TrimExMessage(ExceptionMessage);
end;
end;
Proces ověření ve zkratce:
-
Vyvolání průvodce OAuth2
-
Zadání přihlašovacích údajů
-
V případě úspěšného ověření přesměrování na adresu uvedenou v parametru „redirect_uri“ vlastnosti AuthorizationURL
-
V obsluze události OnResponse získáme autorizační kód, pomocí kterého požádáme o přístupový token
-
Získaný token zašifrujeme s využitím třídy CFxCrypt a uložíme např. do CompanyCache ABRA Gen

Pro agendu Objednávky přijaté (OP) se v okně náhledů zobrazí záložka Objednávka přijatá, kde bude zobrazeno číslo dokladu, celková lokální cena a řádky dané objednávky
Nejprve je třeba vytvořit soubor style.css, který umístíme do instalačního adresář sytému ABRA Gen, podsložky _Nahledy. V našem příkladu se jedná o cestu:
c:/ABRA/INSTALACE/DEVELOP/CS/25.2/AbraGen-25.3.0-cs-CZ-debug-250318-1919-d2fc8a6/_Nahledy/style.css
Ve skriptu níže je tuto cestu třeba nahradit dle vaší potřeby.
Obsah style.css:
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f8f8f8;
color: #333;
}
h1 {
color: #0057a3;
font-size: 24px;
margin-bottom: 10px;
}
h2 {
color: #0057a3;
font-size: 18px;
margin-bottom: 10px;
}
p {
font-size: 14px;
line-height: 1.6;
}
Dále vytvoříme skript v agendě Balíčky skriptů. Druh skriptu bude Aplikační modul - Systémové události.
procedure DocumentsViewer_AddTabs_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject;
const ASiteCLSID: string; var AParams: TNxParameters);
var
mTabData: TNxParameters;
begin
if ASourceObject.CLSID = Class_ReceivedOrder then
begin
mTabData := TNxParameters.Create;
mTabData.GetOrCreateParam(dtString, 'Name').AsString := 'Objednávka přijatá'; // Název záložky
mTabData.GetOrCreateParam(dtString, 'ID').AsString := 'HTMLFormTab_' + ASourceObject.OID;
AParams.AsList.Add(mTabData);
end;
end;
procedure DocumentsViewer_AddContent_Hook(const AContext: TNxContext; const ASourceObject: TNxCustomBusinessObject;
const ASiteCLSID: string; AID: string; var AParams: TNxParameters);
var
mHtml: string;
mMemTableAmount, mMemTableRows: TMemTable;
mInputParams: TNxParameters;
mAmount: Double;
mRowType: Integer;
mText, mStoreCardName, mDisplayText: string;
mOrderNumber: string;
mDocQueueCode, mPeriodCode: string;
begin
if (ASourceObject.CLSID = Class_ReceivedOrder) and (AID = 'HTMLFormTab_' + ASourceObject.OID) then
begin
mInputParams := TNxParameters.Create;
mMemTableAmount := TMemTable.Create(nil);
mMemTableRows := TMemTable.Create(nil);
try
mInputParams.NewFromDataType(dtString, 'ID').AsString := ASourceObject.OID;
// Načtení hlavičkových údajů: Amount, DQ.Code, OrdNumber, P.Code
AContext.SQLSelect2(
'SELECT RO.LOCALAMOUNT, RO.ORDNUMBER, DQ.CODE AS DOCQUEUECODE, P.CODE AS PERIODCODE ' +
'FROM RECEIVEDORDERS RO ' +
'JOIN PERIODS P ON P.ID = RO.PERIOD_ID ' +
'JOIN DOCQUEUES DQ ON DQ.ID = RO.DOCQUEUE_ID ' +
'WHERE RO.ID = :ID',
mMemTableAmount,
mInputParams
);
if not mMemTableAmount.IsEmpty then
begin
mAmount := mMemTableAmount.FieldByName('LOCALAMOUNT').AsFloat;
mDocQueueCode := mMemTableAmount.FieldByName('DOCQUEUECODE').AsString;
mPeriodCode := mMemTableAmount.FieldByName('PERIODCODE').AsString;
mOrderNumber := mDocQueueCode + '-' + IntToStr(mMemTableAmount.FieldByName('ORDNUMBER').AsInteger) + '/' + mPeriodCode;
end
else
begin
mAmount := 0;
mOrderNumber := '[neznámé číslo]';
end;
// Načtení řádků objednávky
AContext.SQLSelect2(
'SELECT RO2.ROWTYPE, RO2.TEXT, SC.NAME ' +
'FROM RECEIVEDORDERS2 RO2 ' +
'LEFT JOIN STORECARDS SC ON SC.ID = RO2.STORECARD_ID ' +
'WHERE RO2.PARENT_ID = :ID',
mMemTableRows,
mInputParams
);
// HTML výstup
mHtml :=
'<!DOCTYPE html>'#13#10 +
'<html>'#13#10 +
'<head>'#13#10 +
' <meta charset="UTF-8">'#13#10 +
' <link rel="stylesheet" href="file:///c:/ABRA/INSTALACE/DEVELOP/CS/25.2/AbraGen-25.3.0-cs-CZ-debug-250318-1919-d2fc8a6/_Nahledy/style.css">'#13#10 +
' <title>Objednávka přijatá</title>'#13#10 +
'</head>'#13#10 +
'<body>'#13#10 +
' <h1>' + mOrderNumber + '</h1>'#13#10 +
' <p><strong>Celková cena (lok.):</strong> ' + FormatFloat('#,##0.00 Kč', mAmount) + '</p>'#13#10 +
' <h2>Řádky:</h2>'#13#10 +
' <ul>'#13#10;
mMemTableRows.First;
while not mMemTableRows.Eof do
begin
mRowType := mMemTableRows.FieldByName('ROWTYPE').AsInteger;
mText := mMemTableRows.FieldByName('TEXT').AsString;
mStoreCardName := mMemTableRows.FieldByName('NAME').AsString;
if mRowType = 3 then
mDisplayText := mStoreCardName
else
mDisplayText := mText;
mHtml := mHtml + ' <li>Typ (' + IntToStr(mRowType) + ') - ' + mDisplayText + '</li>'#13#10;
mMemTableRows.Next;
end;
mHtml := mHtml +
' </ul>'#13#10 +
'</body>'#13#10 +
'</html>'#13#10;
AParams.NewFromDataType(dtString, 'Content').AsString := mHtml;
AParams.NewFromDataType(dtString, 'Format').AsString := 'HTML';
finally
mMemTableAmount.Free;
mMemTableRows.Free;
mInputParams.Free;
end;
end;
end;
begin
end.