Príklady pokročilého skriptovania
Táto kapitola dopĺňa všeobecný úvod do problematiky skriptovania v systéme ABRA Gen o ukážky pokročilých praktických príkladov.
Pokročilé příklady skriptování
Příklady skriptování pro pokročilé
Funkcia SQLSelectFirst slúži na vrátenie prvého riadku výsledku SQL dopytu.
Konkrétne ide o funkcie SQLSelectFirstAsString , SQLSelectFirstAsInteger, SQLSelectFirstAsExtended, SQLSelectFirstAsBlob
Príklad použitia:
..
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 prevod času na UnixTime slúži funkcia DateTimeToUnix.
Príklad použitia:
..
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 verzie 19.4.3 je možné používať parametrizované SQL dopyty zo skriptovania. Používanie parametrov v SQL je jednoduchšie, bezpečnejšie a pre SQL servery aj rýchlejšie na realizáciu vďaka cachovaniu plánov SQL dopytov. Cez parametre je možné ľahko a bezpečne preniesť ľubovoľný dátový typ, teda aj binary blob dáta.
Príklad použitia parametrov pri vykonávaní SQL dopytov (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;
..
Príklad použitia parametrov pri získavaní dát z databázy (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;
..
Funkcia FindPaymentDestByVarsymb slúži na na vyhľadanie platené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 parametrov:
// 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ů v agendě Firemné údaje
// 0 - Vrátit vyhovující dle VS, částky a stavu zaplacení s ohledem na nastavení parametrů v agendě Firemné údaje
// 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
Príklad použitia:
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;
Ide o nasledujúce metódy triedy TNxCustomAccountedDocument:
function AttachSourceGroup(const ASourceGroup_ID: TNxOID);
function RemoveSourceGroup(const ASourceGroup_ID: TNxOID);
function IsSourceGroupAttached(const ASourceGroup_ID: TNxOID): Boolean;
function GetSourceGroup: TNxOID;
Príklad použitia:
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ážkový skript, ktorý pridá riadok 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ážkový skript, ktorý umožňuje rozšíriť zoznam objektov na záložke X-väzby, a to jednak pridaním objektov, tak i celých nových skupín objektov:
{
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ážkový skript, ktorý umožňuje upraviť poznámku zobrazovanú vo vizuálne na záložke X-väzby.:
{
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. Príklad založenia používateľskej väzby zo skriptovania:
{
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áčik umožňuje priamo ovplyvniť SQL. Kedykoľvek číselník potrebuje dáta z databázy, zostavuje sa SQL. Háčik sa volá pred zostavení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'
Príklad:
Chceme, aby používateľ Daniel Rasák (‘4300000101’) videl iba firmy s názvom začínajúcim na A. (Jednoduchý príklad nad demodátami)
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;
Keď nie je dostupný zdroj dát (tabuľka), môžem si súčasne s podmienkou zdroj dát pridať:
Názov aliasu voľte s rozumom, nesmie kolidovať s existujúcimi.
Dajte si pozor, aby join nezmnožil riadky namiesto toho, aby ich obmedzil.
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;
Vo väčšine prípadov si vystačíte s vyššie uvedenými príkladmi.
Máme ale k dispozícii ešte jeden variant, ktorý je zložitý na pochopenie, ale v určitých prípadoch môže zásadne ovplyvniť rýchlosť prevedenia dopytu.
Jadro ho používa napr. pre FULLTEXT.
Skúste použiť, pokiaľ základné WHERE alebo JOIN+WHERE budú 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;
Poddopyt musí spĺňať tieto kritériá:
- vracať jeden stĺpec s názvom Parent_ID
- Parent_ID musí byť ID z číselníka, z ktorého sa získavajú dáta (podmienka obmedzuje proti A.ID
- hodnoty Parent_ID musia byť unikátne
Príklad:
- rozpoznanie miesta objednávka prijatá riadok s ukážkou odovzdania hodnoty z iného poľa
- rozpoznanie X položky na riadku objednávky prijatej
- rozpoznanie, že ide o vizuálnu editáciu z definovateľného formulára
Háčik: Business objekt: Objednávka prijatá - riadok
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áčik: Číselník: Číselník skladových kariet
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;
Doplnenie parametra z definície definovateľného formulára.
Príklad použitia triedy CFxLog s metódou SaveLog, ktorá vytvorí a uloží nový log v agende 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);
..
Príklad ako otvoriť číselník a vybranú hodnotu spracovať:
// 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;
Príklad použitia funkcie GetARESCZData príp. GetSKFirmData - vytvorenie business objektu firmy a zavolanie príslušných funkcií na naplnenie (na vyskúšanie vo vizuálne s interakciou používateľa):
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žnosť v skripte ručne vyvolať výnimku z breakpointov alebo za priameho chodu.
Po stlačení tlačidla Vyvolať výnimku a potvrdenie dialógu, kde je možné zmeniť text výnimky sa zašle do Abry požiadavka na budúcu výnimku. Abra pri ďalšej realizácii umelo zlyhá.
Oproti vyvolaniu výnimky priamo zo skriptovania príkazom RaiseException sa však ale nezobrazuje CallStack, kde chyba vznikla.
Uvedený príklad demonštruje, ako je možné na základe logiky v business objekte (objednávky prijaté) - po nastavení radu dokladu - docieliť zapnutie/vypnutie riadkovej zľavy, a následne zobrazenie/skrytie stĺpcov v gride riadkov tej 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;
Ide o triedy TPDFDocument a TPDFFileAttachment, ktoré umožňujú prácu s prílohami PDF dokumentov. Triedy sú založené na produkte tretej strany SecureBlackBox, ktorý je súčasťou systému ABRA Gen, vie ako načítať existujúce prílohy, tak vložiť 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 príklade potrebujeme preniesť zákazku a projekt z výrobného príkazu do CRM aktivity. V agendách existuje akcia Aktivity - Založiť novú aktivitu a pripojiť, štandardne ale nemáme v skriptingových háčikoch pri otvorení aktivity cez túto akciu k dispozícii zdrojový business objekt (tu výrobný príkaz). Tento príklad ukáže, ako sa dá potrebný BO získať a použiť v nejakom háčiku (tu AfterSiteOpen_Hook).
V _InitSelectionParams_Hook si zapamätáme objekt zdrojového dokladu a z neho potom preberá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.
Tu je príklad, ako zo skriptingu vytvoriť a odoslať e-mail v programe Outlook. Pomocou skriptovania môžeme vytvoriť akýkoľvek zaregistrovaný objekt vo Windows, teda aj napríklad Outlook.Application. Používa sa na to metóda CreateOleObject.
Aké vlastnosti, udalosti alebo metódy objekt používa, je možné nájsť v dokumentácii: 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;
Pri prihlásení sa volá háčik Aplikačný modul: Systémové udalosti - AfterLogon_Hook. Tento háčik sa však nevolá iba pri prvotnom prihlásení používateľa do AbraGen.exe, ale pri každom vytvorení kontextu z klienta na aplikačný server. Na vytváranie viacerých kontextov na aplikačný server (z jednej aplikácie AbraGen.exe ) dochádza od verzie 23.2, a to pri vyhodnotení údajov definovateľných panelov, ktoré teraz prebieha v samostatnom vlákne.
Existencia viacerých vlákien má vplyv aj na použitie globálnej premennej GlobParams, parametre v nej je možné použiť len v rámci jedného vlákna. Hodnotu parametra nastavenú v jednom vlákne tak nie je možné získať v inom vlákne. Aby bolo možné zdieľať hodnoty parametrov medzi rôznymi vláknami, bola vytvorená nová premenná GlobThreadParams.
Nasledujúci príklad demonštruje, ako zabezpečiť, aby sa kód v háčiku AfterLogon_Hook zavolal iba raz (pri prihlásení používateľa do ABRA Gen), a ako hodnotu parametra nastavenú v jednom vlákne (pri prihlásení používateľa do ABRA Gen) získať v inom vlákne (pri vyhodnotení definovateľného panela).
Zároveň je potrebné mať na pamäti, že v háčiku AfterLogon_Hook je možné pristúpiť ku GUI (napríklad zobraziť formulár) iba, ak je kód vyvolaný z hlavného vlákna. Pokiaľ k prístupu ku GUI dôjde z iného vlákna, celá aplikácia môže zamrznúť.
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;
Príklad použitia funkcie GetUserParameters pre získanie používateľských parametrov na business objekte skladu, zmena hodnoty vybraného parametra a uvoľnenie cache pre používateľské parametre pre daný sklad pomocou ClearUserParametersCache:
Príklad použitia:
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;
..
Príklad tvorby JSON objektu s poľami:
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ží nasledujúce 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
}
Príklad spustenia exportu Účtovné exporty - Export predvahy do MS Excel, ktorý vytvára HTML obsah, ktorý je uložený ako súbor s prílohou XLS. Takýto súbor vie následne otvoriť MS Excel a je možné pracovať s hodnotami.
Príklad použitia:
// 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;
V skriptovaní je možné využiť jednoduchý zápis do logu, ktorý sa konfiguruje pomocou štandardného nastavenia cez nexus.cfg. Nastavením je možné ľahko určiť, do akej miery bude logovanie zo skriptovania detailné, pomocou určenia hodnoty úrovne (Level).
Pre zapnutie logovania (nastavenie Enabled=1) nie je potrebné reštartovať klientov ani aplikačný server. Nastavenie si všetky aplikácie raz za cca 60 sekúnd obnovujú.
Prí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 potrebná, aby smerovala na sieťové úložisko, kam majú klienti prístup zápisu.
V prípade chybnej cesty môže dochádzať k veľkému výkonovému spomaleniu celého systému.
Prí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ýsledkom je zápis do súboru s názvom, ktorý obsahuje dátum a čas vzniku súboru, názov aplikácie, proces, identifikátor aplikácie a meno počítača.
Príklad mena vzniknutého logu: 24-03-26 16-32-17-946 AbraGen 16556 JAVU-SRV.JAVU-NTB ABRAGen.log
Obsah súboru z uvedeného prí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)
Príklad demonštruje vytvorenie XML dokumentu s digitálnym podpisom a jeho vloženie do SOAP obálky za účelom komunikácie so SÚKL (Štátny ústav pre kontrolu lieč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;
Pri použití interného OLE sa skriptingu dbajte na jeho uvoľnenie!
Nielen, že neuvoľnené OLE spôsobuje Memory Leaky, ale v kombinácii s prihlásením iného používateľa môže viesť k situácii, kedy systém prestane fungovať, pretože dôjde k čiastočnej likvidácii pôvodného ObjectSpace, ktorý už pre ďalšie použitie nie je plne funkčný !!!
Je potrebné dbať na to, aby boli uvoľnené aj všetky ďalšie objekty, ktoré s OLE môžu ďalej držať, napríklad objekt vytvorený prostredníctvom metódy 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áca s definovateľnými importmi: Definovateľné importy (txt, xls a csv) vyžadujú, aby v importnom súbore bola rozlíšená hlavička a riadky pomocou masky. Pokiaľ tomu tak nie je, sú jednotlivé riadky zo súboru importované ako samostatné doklady.
Pre prípad, keď sú v importných dátach len riadkové položky viac ako jednej hlavičky a zároveň ide o riadky len jedného druhu business objektu, boli vytvorené nové skritptingové funkcie dostupné na triede TNxIEImportDefinition.
Súbor typu TXT:
Funkcia ConvertOnlyRowsTXTData, metóda 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.
Súbor typu CSV a XLS (súbor XLS sa pri importe prevedie na CSV formát):
Funkcia ConvertOnlyRowsCSVData, metóda 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.
Príklad použitia:
Na skladovej karte potrebujeme zobraziť hodnotu predaja za posledných 365 dní. Nechceme však, aby sa táto hodnota načítala dynamicky (napríklad do stĺpca cez NxSQLSelect) ale, aby bola perzistentná. Podľa danej položky je možné aj zoradiť zoznam, prípadne hodnotu použiť pre ďalšie výpočty.
Načítanie bude prebiehať naplánovanou úlohou typu skript. Skript zapíše pre všetky skladové karty predaj za posledných 365 dní. Zápis prebieha sekvenčne, aby nedošlo k uzamknutiu tabuľky StoreCards.
Príklad nastavenia a prevedenia:
Na objekte skladovej karty vytvoríme definovateľnú položku X_Prodej365 typu Číslo.
Položku X_Predaj365 si zobrazíme do stĺpca v agende Skladové karty. Pred prvým prepočtom je pochopiteľne hodnota 0 pri každej skladovej karte – východisková hodnota (z definície definovateľnej položky).
V agende Balíčky skriptov založíme skript na výpočet predajov za posledných 365 dní.
V zdrojovom kóde je možné rýchlo vygenerovať hlavičku skriptu „Volanie z naplánovanej úlohy“.
Success – určuje, či úloha prebehla v poriadku
LogInfoStr – naplánovanej úlohe môžeme vrátiť dodatočné informácie o priebehu, napríklad počet zmenených záznamov, prípadne iné informácie.
Metódu pomenujeme a dopíšeme obsah podľa nasledujúceho prí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 najskôr načíta dáta predaja za posledných 365 dní a hodnoty uloží do MemoryDataset. Potom sekvenčne prejde všetky záznamy datasetu a vykoná zmenu definovateľnej položky X_Predaj365. Tu máme na výber z dvoch možností:
A. Zmena cez SQL Update - priama DB zmena bez zápisu sledovania zmien - obchádza business logiku
B. Zmena cez business logiku - zmeny hodnôt sú viditeľné v sledovaní zmien (ak je nastavené na danej triede).
(v ukážkovom skripte teda ponechať iba jednu z možností)
Založíme novú naplánovanú úlohu typu Skript.
Realizácia naplánovanej úlohy vykoná prepočet predajov a hodnoty zapíše do položky X_Predaj365. V číselníku skladových kariet po občerstvení už máme napočítané hodnoty predajov za posledných 365 dní. Podľa položky X_Predaj365 je možné zoznam aj zoradiť alebo triediť.
Pre agendu Skladové karty sa v okne náhľadov zobrazí záložka Predaj, kde bude na Google Charts grafoch ukázaný predaj
Je potrebné mať vystavané faktúry s tovarom za posledných 14 a 30 dní na rôzne strediská, aby skript zobrazoval dáta.
Druh skriptu je Aplikačný modul - Systémové udalosti.
{
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.
Nasledujúci skript načíta pre okno náhľadov v agende Servisované predmety z definovateľnej extra položky s názvom Folder a dátového typu Znaky cestu k adresáru (cestu zadávame bez úvodzoviek), v ktorom môžeme mať uložené prílohy. Tie sa zobrazia v samostatných záložkách okna náhľadov. Ak je navyše v tomto externom priečinku podpriečinok s názvom Fotodokumentácia, v ktorom sú uložené obrázky, vytvorí z týchto obrázkov skript náhľadovú HTML galériu, ktorá je v okne náhľadov zobrazená ako jedna samostatná záložka.
Výsledok v agende Servisované predmety
Druh skriptu je Aplikačný modul - Systémové udalosti.
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.
Nasledujúci príklad ukazuje, ako získať prístupový token potrebný na autentifikáciu pri komunikácii s novým Firebase Cloud Messaging API (V1) pomocou protokolu OAuth 2.0. Token je generovaný na základe privátneho kľúča servisného účtu Google a obsahuje oprávnenia špecifické pre 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 verzie 25.0.93 je možné využiť tento pomerne rozšírený a bezpečný štandard priamo z prostredia skriptovania pomocou triedy TOAuth2Wizard. Vďaka tomu sme schopní využívať služby poskytovateľov, ktorí podporujú overenie používateľa prostredníctvom tretej strany, prípadne sami používajú vlastný identity server.
Príklad napojenia na službu Everifín, ktorá poskytuje služby otvoreného bankovníctva s licenciou 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 Web 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 overenia v skratke:
-
Vyvolanie sprievodcu OAuth2
-
Zadanie prihlasovacích údajov
-
V prípade úspešného overenia presmerovania na adresu uvedenú v parametri „redirect_uri“ vlastnosti AuthorizationURL
-
V obsluhe udalosti OnResponse získame autorizačný kód, pomocou ktorého požiadame o prístupový token
-
Získaný token zašifrujeme s využitím triedy CFxCrypt a uložíme napr. do CompanyCache ABRA Gen
Pre agendu Objednávky prijaté (OP) sa v okne náhľadov zobrazí záložka Objednávka prijatá, kde bude zobrazené číslo dokladu, celková lokálna cena a riadky danej objednávky
Najprv je potrebné vytvoriť súbor style.css, ktorý umiestnime do inštalačného adresár systému ABRA Gen, podzložky _Nahledy. V našom príklade ide o cestu:
c:/ABRA/INSTALACE/DEVELOP/CS/25.2/AbraGen-25.3.0-cs-CZ-debug-250318-1919-d2fc8a6/_Nahledy/style.css
V skripte nižšie je potrebné túto cestu nahradiť podľa vašej potreby.
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;
}
Ďalej vytvoríme skript v agende Balíčky skriptov. Druh skriptu bude Aplikačný modul - Systémové udalosti.
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.
Toto je príklad skriptu, ktorý vypĺňa text do políčka “Popis zľavy” v závislosti na hodnotách objektu zliav. V tomto skripte je využitý háčik BeforeShowPOSCashDiscountParamsForm_Hook. Háčik je dostupný vo všetkých aplikačných moduloch pokladní. Háčik sa vyvolá pred zobrazením formulára pre zadanie zľavy. V parametroch háčika je odovzdávaný objekt zliav na pokladni a vlastný formulár na zadanie zliav.
{
Vyvolá se před zobrazením formuláře pro zadání slevy.)
}
procedure BeforeShowPOSCashDiscountParamsForm_Hook(AContext: TNxContext; ADiscount: TNxCustomBusinessObject; var APOSCashDiscountParamsForm: TForm);
var
edDiscountText: TEdit;
mDiscountKind: integer;
mDiscountKindText: string;
begin
edDiscountText := TEdit(APOSCashDiscountParamsForm.FindChildControl('edDiscountText'));
//edDiscountText.Text := 'init sleva kód: ' + ADiscount.GetFieldValueAsString('Code');
mDiscountKind := ADiscount.GetFieldValueAsInteger('DiscountKind');
{
0 - Akční sleva
1 - Finanční na zboží automatická
2 - Finanční na řádek
3 - Finanční na zboží
4 - Finanční na doklad
5 - Procentní na doklad
6 - Automatická procentní na zboží na dokladu
7 - Procentní řádková
8 - Procentní řádková na zboží
9 - Procentní...
}
case mDiscountKind of
0: mDiscountKindText := 'Akční';
1..4: mDiscountKindText := 'Finanční';
5..9: mDiscountKindText := 'Procentní';
else
mDiscountKindText := 'Mimo rozsah';
end;
edDiscountText.Text := ADiscount.GetFieldValueAsString('Code') + '-' + mDiscountKindText + ': ' + ADiscount.GetFieldValueAsString('DiscountDescription') + ' (' + IntToStr(mDiscountKind) + ')';
end;
begin
end.;
Príklady použitia NxPrintByIDs, NxPrintByConditions:
procedure PrintReports_Test(Sender: TControl);
var
mContext: TNxContext;
i: Integer;
mSite: TSiteForm;
mSCList: TStringList;
mConditions, mCondParams, mCondParamsValues, mExtraParams: TNxParameters;
begin
mSite := TSiteForm(TComponent(Sender).Site);
mContext := NxCreateContext(mSite.BaseObjectSpace);
try
mConditions := TNxParameters.Create;
try
mSCList := TStringList.Create;
try
//Tisk Reportu (Agenda Reporty)
//Nastavení omezení reportu za datum, sklady a skladové karty
//Omezení za Datum
mCondParams := mConditions.NewFromDataType(dtList, 'Date').AsList;
mCondParams.NewFromDataType(dtInteger, 'UsedKind').AsInteger := ckRange;
mCondParamsValues := mCondParams.NewFromDataType(dtList, 'Values').AsList;
// Omezení za datum je sice typu ckRange, ale jako údaj pro omezení (k datu)
// se bere jenom hodnota "LOW"
mCondParamsValues.NewFromDataType(dtFloat, '{:LOW}').AsFloat := 43449; // 15.12.2018
//mCondParamsValues.NewFromDataType(dtFloat, '{:HIGH}').AsFloat := Date; // dnes
// Omezení za sklady (výběr seznamem - řetězec reprezentující seznam ID - oddělené Entrem)
mCondParams := mConditions.NewFromDataType(dtList, 'Store_ID').AsList;
mCondParams.NewFromDataType(dtInteger, 'UsedKind').AsInteger := ckList;
mCondParams.NewFromDataType(dtString, 'ValueList').AsString := '2100000101'#13#10'3500000101';
// Omezení za skladové karty (výběr seznamem - TStringList)
// StringList naplníme přes SQL SELECT
mSite.BaseObjectSpace.SQLSelect('SELECT ID FROM StoreCards WHERE Code LIKE ''0%''', mSCList);
mCondParams := mConditions.NewFromDataType(dtList, 'StoreCard_ID').AsList;
mCondParams.NewFromDataType(dtInteger, 'UsedKind').AsInteger := ckList;
mCondParams.NewFromDataType(dtString, 'ValueList').AsString := NxStringsToCkListStr(mSCList);
//Provede tisk přímo na tiskárnu dle omezení v mConditions - Report "Stav skladu k datu"
// tisk přímo na tiskárnu
NxPrintByConditions(mContext,
mConditions, // Omezení pro report - podmínky
'DCGGWH4VRREL3FWD002BG34ZPK', // DynSQL - "Sklad - Stav k datu"
'V700000001', // Report ID - "Stav skladu k datu"
rtoPrint, // Typ operace - Tisk na tiskárnu
pekARP, // Typ exportu
'KONICA MINOLTA C223', // Výstup - název tiskárny
''); // (slouží pro název souboru v případě tisku do souboru)
//Provede tisk do souboru PDF dle omezení v mConditions - Report "Stav skladu k datu"
// tisk do souboru PDF
NxPrintByConditions(mContext,
mConditions, // Omezení pro report - podmínky
'DCGGWH4VRREL3FWD002BG34ZPK', // DynSQL - "Sklad - Stav k datu"
'V700000001', // Report ID - "Stav skladu k datu"
rtoFile, // Typ operace - Tisk na tiskárnu
pekPDF, // Typ expByCoortu
'C:\ABRA', // Výstup - cesta k souboru (zadávat formát C:\ABRA\)
'Report Skladu - Stav k datu.pdf'); // Název souboru
//Tisk tiskových sestav dle identifikací objektů
//S rozšířeným nastavením tiskárny (Collate, Duplex)
// Rozšířené nastavení riskárny
mExtraParams := TNxParameters.Create;
try
// Zapneme kompletování kopií
// true -> kopie budou tisknuty 1,2,3 1,2,3
// false -> kopie budou tisknuty 1,1 2,2 3,3
mExtraParams.GetOrCreateParam(dtBoolean, 'REPORT_COLLATE').AsBoolean := True;
// Zapneme oboustranný tisk
mExtraParams.GetOrCreateParam(dtBoolean, 'REPORT_DUPLEX').AsBoolean := True;
//Provede tisk tiskové sestavy “Seznam zboží (kód)” přímo na tiskárnu - omezení je dáno seznamem ID skladových karet
// tisk tiskové sestavy
NxPrintByIDs(mContext,
mSCList, // Seznam ID skladových karet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Seznam zboží (kód)"
rtoPrint, // Typ operace - Tisk na tiskárnu
pekARP, // Typ expByCoortu
'KONICA MINOLTA C223', // Výstup - cesta k souboru
'', // Název souboru
2, // Počet kopií
false, // Emulovat kopie
mExtraParams); // Předává parametry pro tisk - Duplex nebo Collate
//Provede tisk tiskové sestavy “Seznam zboží (kód)” do souboru PDF - omezení je dáno seznamem ID skladových karet
// tisk tiskové sestavy.do souboru
NxPrintByIDs(mContext,
mSCList, // Seznam ID skladových karet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Seznam zboží (kód)"
rtoFile, // Typ operace - Tisk na tiskárnu
pekPDF, // Typ expByCoortu
'C:\ABRA', // Výstup - cesta k souboru (zadávat formát C:\ABRA\)
'Seznam zboží(kód).pdf', // Název souboru
2); // Počet kopií
// Provede tisk tiskové sestavy "Seznam zboží (kód)" do souboru PDF - omezení je dáno seznamem ID skladových karet
// Parametr tiskárny při tisku do souboru způsobí, že se vynutí převzetí nastavení papíru z této tiskárny
CFxReportManager.PrintByIDs(mContext,
mSCList, // Seznam ID skladových karet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Seznam zboží (kód)"
rtoFile, // Typ operace - Tisk na tiskárnu
pekPDF, // Typ exoortu
'C:\ABRA\test\develop', // Výstup - cesta k souboru
'Seznam zboží(kód)_printer_KM.pdf', // Název souboru
1, false, nil, // Počet kopií + emulateCopies + AParams
'KONICA MINOLTA C223'); // Název tiskárny, z které se přebírá nastavení stránky/papíru
finally
mExtraParams.Free;
end;
finally
mSCList.Free;
end;
finally
mConditions.Free;
end;
finally
mContext.Free;
end;
end; Nastavenie kompletizácie a obojstranná tlač sa odovzdáva pomocou parametra triedy TNxParameters.
Emulácia kópií sa používa pre tlačiarne, ktoré nepreberajú počet kópií.
POZOR: Ak tlačiareň umožňuje preberanie počtu kópií, majte parameter EmulateCopies vypnutý. V opačnom prípade bude tlačiareň tlačiť exponenciálny počet kópií, miesto 2 vytlačí 4, miesto 3 vytlačí 9 atď.
Funkce NxPrintByConditions umožňuje tisk Reportů (agenda Reporty) dle zadaného omezení, například Stav skladu k datu , Prodané zboží dle skladů, Obraty účtů atd.
Parameter určujúci názov tlačiarne Počet kópií a emulácie boli pridané ako parametre funkcie už vo verzii 19.0.
Od verzie 25.3 bola pridaná možnosť výberu tlačiarne pri tlači do súboru - tým sa vynúti prevzatie nastavenia.
Príklad na pridanie náhľadu obrázku skladovej karty do ľavej časti stromu kusovníka. Pri prechode položkami stromu sa v ľavej časti zobrazuje obrázok z danej skladovej karty. Pokiaľ položka obrázok nemá, načítame vopred pripravený obrázok no_image. To môže byť napríklad biely obrázok alebo upozornenie, že obrázok nebol načítaný.
{
Vyvolává se po provedení inicializace agendy/formuláře. V tento okamžik je již na formuláři dostupný SiteContext.
}
procedure InitSite_Hook(Self: TSiteForm);
var
TreeView: TVirtualStringTree;
begin
With TPanel.Create(Self) do
begin
Parent:= TTabSheet(Self.FindChildControl('tabTree'));
Align:= alLeft;
Width:= 600;
Name:= 'Tree_Pict';
Caption:= '';
end;
with TImage.Create(TPanel(Self.FindChildControl('Tree_Pict'))) do
begin
Parent:= TPanel(Self.FindChildControl('Tree_Pict'));
Name:= 'pctStoreCard_Picture';
Align:= alClient;
AutoSize:=True;
end;
TreeView:= TVirtualStringTree(TTabSheet(Self.FindChildControl('tabTree')).FindChildControl('TreeView'));
if Assigned(TreeView) then
begin
TreeView.OnAfterFocusChanged := @My_OnAfterFocusChanged;
end;
end;
{
Vyvolá se před zobrazením formuláře pro zadání slevy.)
}
procedure My_OnAfterFocusChanged(Sender: TObject);
var
mPicture: TNxCustomBusinessObject;
mStream: TMemoryStream;
mBO: TNxCustomBusinessObject;
mSQL,mID: String;
begin
try
if Assigned(TImage(TVirtualStringTree(Sender).GetParentForm.FindChildControl('pctStoreCard_Picture'))) then
if TVirtualStringTree(Sender).GetFocusedNodeTextByColumn(0) <> '' then
begin
mBO:= TVirtualStringTree(Sender).Site.BaseObjectSpace.CreateObject(Class_StoreCard);
try
// TVirtualStringTree(Sender).GetFocusedNodeTextByColumn(0) - získá z prvního sloupce kód skladové karty
mSQL:= 'Select ID '+
'From StoreCards '+
'Where Hidden = ''N'' and Code = '+QuotedStr(TVirtualStringTree(Sender).GetFocusedNodeTextByColumn(0));
mID:= TVirtualStringTree(Sender).Site.BaseObjectSpace.SQLSelectFirstAsString(mSQL,'');
// Obrázek uložený na Skl. Kartě
if Not NxIsEmptyOID(mID) then
begin
mBO.Load(mID,nil);
mStream:= TMemoryStream.Create;
mPicture:= TVirtualStringTree(Sender).Site.BaseObjectSpace.CreateObject(Class_Picture);
try
if mPicture.Test(mBO.GetFieldValueAsString('Picture_ID')) then
begin
mPicture.Load(mBO.GetFieldValueAsString('Picture_ID'),nil);
mStream.SetBytes(mPicture.GetFieldValueAsBytes('BlobData'));
TImage(TVirtualStringTree(Sender).Site.FindChildControl('pctStoreCard_Picture')).Picture.LoadMultiFormatFromStream(mStream);
end
else
// Pokud položka nemá obrázek, načteme předem připraven obrázek no_image - to může být například upozornění, že neobsahuje obrázek nebo prázdný obrázek
TImage(TVirtualStringTree(Sender).Site.FindChildControl('pctStoreCard_Picture')).Picture.LoadFromFile('.\no_image.png');
finally
mPicture.Free;
mStream.Free;
end;
end;
finally
mBO.Free;
end;
end;
except
// Zahazuju chyby - případně vyvést chybu pomocí RaiseException
// nebo do okna s obrázkem vypsat text chyby (ExceptionMessage)
end;
end;
null a existencia parametra v objekte TJSONSuperObject
Tento príklad ukazuje, ako v prostredí skriptovania testovať, či má parameter v objekte TJSONSuperObject hodnotu null, a ako takú hodnotu výslovne zapísať. Zároveň ukazuje, ako overiť existenciu parametra, ktorý môže alebo nemusí byť vo vstupnom JSONe prítomný.
var
mSQL: string;
mJSON: TJSONSuperObject;
mO: TJSONSuperObject;
begin
mJSON := TJSONSuperObject.Create;
try
mJSON.O['hodnotaNull'] := TJSONSuperObject.CreateByDataType(jtNull); // Nastavení null hodnoty
mJSON.I['cislo_cele'] := 12345;
ShowMessage(mJSON.AsJson);
mO := mJSON.O['neexistuje'];
if not mO.Exists then // Test existence parametru
ShowMessage('O[''neexistuje''].Exists = False');
if mJSON.O['hodnotaNull'].DataType = jtNull then // Test null hodnoty
ShowMessage('mJSON.O[''hodnotaNull''].DataType = jtNull');
finally
mJSON.Free;
end;
end;
Výsledok skriptu:
NxScriptDebuggerBreakPoint pro zastavení kódu ve ScriptDebuggeru
Následující příklad, který do agendy Adresář firem přidává tlačítko pro hromadnou tvorbu firem, slouží hlavně jako ukázka, jak použít proceduru NxScriptDebuggerBreakPoint, která, pokud je k systému ABRA Gen připojen nástroj ScriptDebugger, zastaví kód na vybraném místě.
Vytvoříme nový balíček skriptů a na záložce projekt vytvoříme druh skriptu Knihovna a pojmenuje me ji CreateObjects:
function CreateFirm(AObjectSpace: TNxCustomObjectSpace; AName: String): TNxOID;
var
mBO: TNxCustomBusinessObject;
begin
mBO := AObjectSpace.CreateObject(Class_Firm);
try
NxScriptDebuggerBreakPoint;
mBO.New;
mBO.Prefill;
mBO.SetFieldValueAsString('Name', AName);
mBO.Validate;
mBO.Save;
Result := mBO.OID;
finally
mBO.Free;
end;
end;
begin
end.
Dále vytvoříme druh skriptu Agenda a vybereme Adresář firem:
uses
'CreateObjects';
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Hromadná tvorba firem';
mAction.Category := 'tabList';
mAction.OnExecute := @ButtonCreateFirmsClick;
end;
procedure ButtonCreateFirmsClick(Sender : TObject);
var
I: Integer;
mSite : TSiteForm;
mCount: Integer;
begin
mSite := TComponent(Sender).Site;
NxScriptDebuggerBreakPoint;
mCount := StrToInt(InputBox('Hromadná tvorba firem', 'Zadejte počet', '10'));
for I:=0 to mCount - 1 do
CreateFirm(mSite.BaseObjectSpace,'Generated firm ' + IntToStr(I));
end;
begin
end.
Pokud je následně k systému ABRA Gen připojen ScriptDebugger (viz daná kapitola), zastaví se po spuštění funkce Hromadná tvorba firem skript na daném breakpointu.
Následující příklad přidává do agendy Skladové karty tlačítko, které při stisku ukazuje použití skriptingových funkcí pro správu bezpečného úložiště:
Vytvoříme nový balíček skriptů a na záložce projekt vytvoříme druh skriptu Agenda a pojmenuje me ji SecureStore:
procedure Main(Sender : TObject);
var
mSite: TSiteForm;
mObjectSpace: TNxCustomObjectSpace;
mKeyValue: String;
begin
// Funkce pro bezpečné úložiště jsou na ObjectSpace
mSite := TComponent(Sender).Site;
mObjectSpace := mSite.BaseObjectSpace;
// Pokud klíč existuje, pak načteme hodnotu do proménné a zobrazíme ji
if mObjectSpace.ReadFromSecureStore('mujklic', mKeyValue) then
begin
// Vypsání načtené hodnoty
ShowMessage(mKeyValue);
// Tímto voláním klíč opět vymažeme
mObjectSpace.DeleteFromSecureStore('mujklic');
ShowMessage('Klíč byl odstraněn');
end else
begin
// Vytvoření klíče "mujklic" s hodnotou aktuálního data
// Po spuštění tohoto kódu si můžeme hodnotu klíče zobrazit v nástroji AppServerProp.exe > Funkce > Bezpečné úložiště.
mObjectSpace.WriteToSecureStore('mujklic', DateTimeToStr(Now));
ShowMessage('Klíč byl založen a můžete jej uživatelsky zobrazit v nástroji AppServerProp.exe');
// Opětovným zápisem provedeme přepsání již uložené hodnoty
mObjectSpace.WriteToSecureStore('mujklic', 'Opětovně změněno: ' + DateTimeToStr(Now));
end;
// Pokud chceme, aby hodnota klíče byla uložena pro konkrétního uživatele, musíme do názvu klíče uložit identifikátor uživatele
// Seznam existujících zakázkových klíčů nelze načíst. Je k dispozici pouze správcům v nástroiji AppServerProp.exe
mObjectSpace.WriteToSecureStore('Klíč na uživatele/' + NxGetActualUserID(mObjectSpace), DateTimeToStr(Now));
end;
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction, mAction2: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Bezpečné úložiště';
mAction.Category := 'tabList,tabDetail';
mAction.OnExecute := @Main;
end;
begin
end.
Další způsoby správy bezpečného úložiště (Web API a AppServerProp) viz kap. Bezpečnost dat.
Je připraven nový skriptingový háček MassInvoicing_InputGrouping_Hook. Háček je dostupný v aplikačním modulu "Systémové události". Vyvoláva sa pri vykonávaní hromadnej fakturácie faktúr vydaných z objednávok prijatých.
Háčik umožňuje upraviť zoznam položiek hlavičiek objednávok prijatých (OP), podľa ktorých sa budú skupinovať vytvárané doklady faktúr vydaných (FV). V parametri AGroupList je možné upraviť zoznam názvov položiek business objektu hlavičiek OP pre skupinovanie FV.
Odovzdaný zoznam je už predvyplnený typickými položkami pre skupinovanie: Currency_ID, TradeType, PricesWithVAT, VATDocument, OnlyWholeOrder.
Zoznam položiek je možné v skriptingu ľubovoľne zmeniť.
procedure MassInvoicing_InputGrouping_Hook(const AContext: TNxContext; var AGroupList: TStringList);
begin
AGroupList.Delete(AGroupList.IndexOf('OnlyWholeOrder'));
//AGroupList.Add('TransportationType_ID');
//AGroupList.Add('Amount');
AGroupList.Add('DocDate$DATE');
end;
Následující příklad ukazuje, jak lze do agendy definovatelných položek přidat tlačítko, které provede synchronizaci definovatelných položek napříč předem definovanými databázovými spojeními. Skript využívá rozhraní Web API a jeho endpoint /installsets/distribution, který umožňuje jednoduchý export a import různých systémových nastavení. Podrobnější popis tohoto API endpointu a jeho možností naleznete v kapitole PŘÍKLAD 22 - Synchronizace definic mezi spojeními.
Skript nejprve z aktuálního spojení vyexportuje definovatelné položky (pomocí GET požadavku na API), a následně je naimportuje do ostatních definovaných spojení (pomocí POST požadavků).
Druh skriptu: Agenda - Definovatelné položky.
const
mAPIUrl = 'http://localhost/';
mUser = 'Supervisor';
mPass = '';
mGetRoute = '/installsets/distribution?specific=userdeffieldsx';
mPostRoute = '/installsets/distribution';
mConnections = ['Demodata', 'Demodata2', 'Demodata3', 'Demodata4'];
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction, mAction2: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Synchronize';
mAction.Category := 'tabList';
mAction.OnExecute := @Main;
end;
procedure Main(Sender : TObject);
var
mWinHttpRequest: Variant;
mSite: TSiteForm;
mAuth, mTemp: String;
mBytes : TBytes;
I : Integer;
mMS: TMemoryStream;
begin
mSite := TComponent(Sender).Site;
mAuth := 'Basic ' + EncodeBase64(TEncoding.UTF8.GetBytes(mUser + ':' + mPass));
mWinHttpRequest := CreateOleObject('WinHttp.WinHttpRequest.5.1');
mWinHttpRequest.Open('GET', mAPIUrl + mSite.CentralCache.ConnectionName + mGetRoute, False);
mWinHttpRequest.SetTimeouts(5000, 5000, 5000, 600000); // 600000 ms = 10 minutes for receiving the response
mWinHttpRequest.SetRequestHeader('Authorization', mAuth);
mWinHttpRequest.Send;
if not ((mWinHttpRequest.Status = 200) or (mWinHttpRequest.Status = 201)) then
begin
ShowMessage('Request failed with status code: ' + IntToStr(mWinHttpRequest.Status) + nxCrLf + TEncoding.UTF8.GetString(mWinHttpRequest.ResponseBody));
exit;
end;
mBytes := mWinHttpRequest.ResponseBody;
mMS := TMemoryStream.Create;
try
mMS.SetBytes(mBytes);
mMS.SaveToFile('c:\Nexus\_Devel_1\response.zip');
finally
mMS.Free;
end;
mTemp := '';
for I := 0 to Length(mConnections) - 1 do
begin
if mConnections[I] <> mSite.CentralCache.ConnectionName then
begin
mWinHttpRequest.Open('POST', mAPIUrl + mConnections[I] + mPostRoute, False);
mWinHttpRequest.SetRequestHeader('Authorization', mAuth);
mWinHttpRequest.SetRequestHeader('Content-Type', 'application/zip');
mWinHttpRequest.Send(mBytes);
mTemp := mTemp + mConnections[I] + ': ';
mTemp := mTemp + FloatToStr(mWinHttpRequest.Status) + ' ';
if mWinHttpRequest.ResponseText <> '' then
begin
mTemp := mTemp + '- ' + TEncoding.UTF8.GetString(mWinHttpRequest.ResponseBody);
end;
mTemp := mTemp + nxCrLf;
end;
end;
ShowMessage(mTemp);
end;
begin
end.
Popis problému
Při volání SQL dotazu ze skriptu může dojít k chybě "Byla překročena max. velikost alokované paměti pro řádek datasetu". Tento problém se týká všech databázových platforem (MSSQL, Oracle, Firebird) a dochází k němu, pokud dotaz vrací více sloupců, jejichž obsah je upraven funkcemi. Ať už se jedná o funkci FORMAT na MSSQL, TO_CHAR na Oracle nebo zřetězení pomocí + či ||, databázový server často implicitně přiřadí výslednému sloupci maximální textový datový typ (např. NVARCHAR(4000)). I když skutečná data jsou krátká, součet maximálních délek všech sloupců v jednom řádku může překročit limit datasetu v aplikaci (typicky 65 536 bajtů).
Chybná konstrukce
Následující dotaz připravuje data pro tiskový výstup. Formátuje několik číselných a textových polí. Každý z těchto upravených sloupců je databází vrácen s velkým implicitním datovým typem, což ve výsledku způsobí přetečení paměti pro řádek.
SELECT
'FA-' + OrdNumber AS InvoiceID,
UPPER(FirmName) AS CustomerName,
REPLACE(FORMAT(TotalAmount, 'N2'), ',', '.') + ' CZK' AS FormattedTotal,
REPLACE(FORMAT(TotalVAT, 'N2'), ',', '.') + ' CZK' AS FormattedVAT,
'Splatnost: ' + FORMAT(DueDate, 'dd.MM.yyyy') AS DueDateInfo
FROM
IssuedInvoices
WHERE
IsStorno = 0
Důsledky
Zatímco dotaz se v nástrojích pro správu databáze (např. SQL Management Studio) provede bez problémů, při spuštění ze skriptu v aplikaci selže s chybou o překročení velikosti řádku. Důvodem je, že skriptovací prostředí alokuje paměť podle definice sloupců, nikoliv podle skutečné délky dat. Součet teoretických délek (např. 5x NVARCHAR(4000)) snadno překročí povolený limit.
Optimální řešení
Řešením je explicitně definovat datový typ a jeho velikost pro každý transformovaný sloupec pomocí funkce CAST. Tím zajistíme, že celková definovaná velikost řádku zůstane v limitu datasetu. Funkce CAST je standardní součástí SQL a je dostupná na všech podporovaných databázích.
SELECT
CAST('FA-' + OrdNumber AS VARCHAR(50)) AS InvoiceID,
CAST(UPPER(FirmName) AS VARCHAR(150)) AS CustomerName,
CAST(REPLACE(FORMAT(TotalAmount, 'N2'), ',', '.') + ' CZK' AS VARCHAR(40)) AS FormattedTotal,
CAST(REPLACE(FORMAT(TotalVAT, 'N2'), ',', '.') + ' CZK' AS VARCHAR(40)) AS FormattedVAT,
CAST('Splatnost: ' + FORMAT(DueDate, 'dd.MM.yyyy') AS VARCHAR(30)) AS DueDateInfo
FROM
IssuedInvoices
WHERE
IsStorno = 0
Jak snadno zjistit, které sloupce problém způsobují? V nástroji pro správu databáze lze dotaz dočasně upravit tak, aby jeho výsledek vložil do nové tabulky (např. pomocí klauzule INTO new_table na MSSQL). Následnou inspekcí sloupců nově vytvořené tabulky lze odhalit, jaké datové typy databáze automaticky přiřadila, a které je tedy potřeba omezit pomocí CAST.
Skriptovací systém byl rozšířen o podporu automatické dokumentace API funkcí, kterou lze volat přímo z API. Dokumentace je registrována přímo v balíčcích skriptů společně s implementací API funkcí.
Dokumentační procedury mají název s prefixem OPENAPIDOC_ následovaným názvem API funkce. Hlavičku dokumentace lze také automaticky vygenerovat pomocí akce „vygenerovat hlavičku → volání OpenAPI dokumentace funkce“, která vytvoří základní strukturu pro definici dokumentace.
Dokumentace se vytváří pomocí objektu ARoutesBuilder, který umožňuje definovat:
- jednotlivé HTTP routy,
- HTTP metody (GET, POST, PUT, DELETE),
- parametry (query, path, header),
- formát a obsah odpovědí,
- strukturu a obsah těla požadavku,
- příklady JSON dat,
- JSON schémata pro validaci těla dotazu.
Příklad založení dokumentace s názvem OPENAPIDOC_xxx
procedure OPENAPIDOC_xxx(AContext:TNxContext; ARoutesBuilder: TApiRoutesBuilder);
const
cSchema =
'{' +
'"$schema": "http://json-schema.org/draft-05/schema#" ,' +
'"name": "UserData",' +
'"type": "object",' +
'"properties": {' +
'"id": {' +
'"type": "string"' +
'},' +
'"name": {' +
'"type": "string"' +
'},' +
'"age": {' +
'"type": "integer", "minimum": 0' +
'},' +
'"email": {' +
'"type": "string", "format": "email"' +
'},' +
'"isActive": {' +
'"type": "boolean", "default": true' +
'},' +
'"roles": {' +
'"type": "array", "items": { "type": "string" }' +
'}' +
'},' +
'"required": ["name", "email"]' +
'}';
cExample =
'{' +
' "id": "1",' +
' "name": "Alice",' +
' "age": 30,' +
' "email": "alice@example.com",' +
' "isActive": false,' +
' "roles": ["admin", "user"]' +
'}';
begin
ARoutesBuilder
.AddRoute('/' )
.Get('Ziska seznam zaznamu')
.AddResponses
.Ok('Zaznamy byly ziskany')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.Post('Založí zaznam metodou POST')
.AddParams
.BooleanParam('Force', 'Parametr vynutí uložení objektu i přes softvalidace', False, rplQuery)
.FinishParams
.AddRequestBody
.JSONContent(cSchema, cExample)
.FinishRequestBody
.AddResponses
.Created('Byl vytvoren novy zaznam')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.FinishRoute
.AddRoute('/{id}')
.Put('Modifikuje záznam')
.AddParams
.StringParam('id', 'id zaznamu', True, rplPath)
.BooleanParam('Force', 'Parametr vynutí uložení objektu i přes softvalidace', False, rplQuery)
.FinishParams
.AddRequestBody
.JSONContent(cSchema, cExample)
.FinishRequestBody
.AddResponses
.Ok('Zaznam byl modifikovan')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.Delete('Smaze zaznam')
.AddParams
.StringParam('id', 'id zaznamu', True, rplPath)
.FinishParams
.AddResponses
.NoContent('Zaznam byl smazan')
.FinishMethod
.FinishResponses
.FinishDefinition
.FinishRoute;
end;
Příklad demonstruje manipulaci s PDF dokumenty za použití metod třídy CFxPDF. Kód je navržen k použití v dokladové agendě (Faktúry vydané) a navazuje funkci na tlačítko.
Konkrétně ukazuje, jak:
- Dynamicky vygenerovat PDF z aktuálně vybraného dokladu pomocí CFxReportManager.PrintByIDsToBytes.
- Připojit k tomuto vygenerovanému PDF další externí PDF soubor z disku (CFxPDF.MergeInStream), čímž dojde ke spojení dokumentů do jednoho celku.
- Vytvořit vodoznak překrytím všech stránek výsledného dokumentu dalším PDF z disku (CFxPDF.Overlay).
- Zabezpečit finální dokument heslem a omezit práva pro práci se souborem (ponecháno pouze povolení tisku) (CFxPDF.Secure).
Všechny metody třídy CFxPDF jsou dostupné ve variantě pro práci se soubory na disku (String) a s objekty TMemoryStream.
Pro správnou funkčnost je nutné mít skript zařazen pod Druh skriptu: Agenda a vybranou Třídu objektu: Faktúry vydané (IssuedInvoices). Kód automaticky získává ID aktuálně vybraného dokladu pomocí přetypování na TDynSiteForm.
Príklad použitia:
procedure RunPDFTest(Sender: TObject);
var
mSite: TSiteForm;
mOS: TNxCustomObjectSpace;
mContext: TNxContext;
mIDList: TStrings;
mInputA, mInputB, mOutput: TMemoryStream;
mInputStreams: TObjectList;
mPDFtoAppendPath,
mOutputPath,
mOutputWithWatermarkPath,
mOutputSecuredPath,
mOverlayPath: String;
mCurrentOID: String;
begin
mSite := TComponent(Sender).Site;
// 1. KONTROLA a ZÍSKÁNÍ ID AKTIVNÍ FAKTURY
if (TDynSiteForm(mSite).CurrentObject = nil) then
begin
NxShowSimpleMessage('Nejdříve vyberte fakturu v seznamu nebo detailu!', mSite);
Exit;
end;
mCurrentOID := TDynSiteForm(mSite).CurrentObject.OID;
mOS := mSite.BaseObjectSpace;
mContext := NxCreateContext(mOS);
// Cesty k souborům - UPRAVTE DLE POTŘEBY. Soubory k načtení MUSÍ existovat!
mPDFtoAppendPath := 'C:\temp\PDFToAppend.pdf';
mOverlayPath := 'C:\temp\PDFWatermark.pdf';
mOutputPath := 'C:\temp\PDFOutput.pdf';
mOutputWithWatermarkPath := 'C:\temp\PDFOutputWatermarked.pdf';
mOutputSecuredPath := 'C:\temp\PDFOutputFinal.pdf'; // Finální zabezpečený výstup
try
mIDList := TStringList.Create;
try
mIDList.Add(mCurrentOID);
mInputStreams := TObjectList.Create(False);
try
mInputA := TMemoryStream.Create;
try
// 2. Vygenerování PDF z reportu do paměti (mInputA)
// ZDE VLOŽTE ID SVOJÍ DEFINICE REPORTU A VARIANTY PRO FAKTURY!
mInputA.SetBytes(
CFxReportManager.PrintByIDsToBytes(
mContext,
mIDList,
'40SBPEINEFD13ACM03KIU0CLP4', // DataSource reportu (Definice) z Tiskových sestav
'W400000001', // ID reportu (Varianta) - aktualizujte dle potřeby
pekPDF)
);
mInputB := TMemoryStream.Create;
try
// 3. Načtení přílohy z disku (MANDATORY FILE)
if not FileExists(mPDFtoAppendPath) then
RaiseException('Soubor s přílohou neexistuje: ' + mPDFtoAppendPath);
mInputB.LoadFromFile(mPDFtoAppendPath);
// 4. Spojení (Merge)
mInputStreams.Add(mInputA);
mInputStreams.Add(mInputB);
mOutput := CFxPDF.MergeInStream(mInputStreams);
try
mOutput.SaveToFile(mOutputPath);
finally
mOutput.Free;
end;
finally
mInputB.Free;
end;
finally
mInputA.Free;
end;
finally
mInputStreams.Free;
end;
// 5. Vodoznak (Overlay) - pokud existuje překryvný soubor
if FileExists(mOverlayPath) then
begin
CFxPDF.Overlay(
mOutputPath,
mOverlayPath,
mOutputWithWatermarkPath,
True // Překrytí všech stránek první stránkou Overlay PDF
);
end
else
begin
mOutputWithWatermarkPath := mOutputPath;
end;
// 6. Zabezpečení (Secure)
CFxPDF.Secure(
mOutputWithWatermarkPath,
mOutputSecuredPath,
'', // OldPassword (necháme prázdné)
'', // UserPassword (heslo pro otevření dokumentu)
'owner123', // OwnerPassword (heslo pro práva/změnu práv)
False, // AllowAccessibility
False, // AllowExtract
False, // AllowAssemble
False, // AllowAnnotateForm
False, // AllowFormFill
False, // AllowModifyOther
True // Povolit tisk (AAllowPrint)
);
NxShowSimpleMessage('Hotovo. Faktura s přílohou uložena do: ' + mOutputSecuredPath, mSite);
finally
mIDList.Free;
end;
finally
mContext.Free;
end;
end;
// Inicializační háček - přidání tlačítka do agendy
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Tisk Faktury s přílohou (PDF)';
mAction.Category := 'tabList';
mAction.OnExecute := @RunPDFTest;
end;
begin
end.