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é príklady skriptovania
Príklady skriptovania pre pokročilých
Funkcia SQLSelectFirst slúži na vrátenie prvého riadka výsledku SQL dotazu.
Konkrétne ide o funkcie SQLSelectFirstAsString, SQLSelectFirstAsInteger, SQLSelectFirstAsExtended, SQLSelectFirstAsBlob
Príklad použitia:
..
mBlob:=Self.ObjectSpace.SQLSelectFirstAsBlob('SELECT NOTE FROM FIRMS'); //Vráti hodnotu ako TBytes
mString:=Self.ObjectSpace.SQLSelectFirstAsString('SELECT NAME FROM FIRMS'); //Vráti hodnotu ako String
mInt:=Self.ObjectSpace.SQLSelectFirstAsInteger('SELECT CODE FROM FIRMS'); //Vráti hodnotu ako Integer
mExt:=Self.ObjectSpace.SQLSelectFirstAsExtended('SELECT PENALTYPERCENT FROM FIRMS'); //Vráti hodnotu ako Extended
//Hodnoty po vykonaní funkcie sú nasledujúce
//mBlob = ABRA Software s.r.o. je technologická firma...
//mString = ABRA Software s.r.o.
//mInt = 00001
//mExt = 1,25
..
Na prevod času na UnixTime slúži funkcia DateTimeToUnix.
Príklad použitia:
..
mDateTimeNow1:= DateTimeToUnix(Now(), True); //vracia výsledok UnixTime s časovým posunom
mDateTimeNow2:= DateTimeToUnix(Now(), False); //vracia výsledok UnixTime v UTC
//Hodnoty po vykonaní funkcie sú nasledujúce
//mDateTimeNow1 = 1665079779
//mDateTimeNow2 = 1665072579
..
Od verzie 19.4.3 je možné používať parametrizované SQL dotazy zo skriptovania. Používanie parametrov v SQL je jednoduchšie, bezpečnejšie a pre SQL servery aj rýchlejšie na vykonanie vďaka cachovaniu plánov SQL dotazov. Cez parametre možno jednoducho a bezpečne preniesť ľubovoľný dátový typ, teda aj binary blob dáta.
Príklad použitia parametrov pri vykonávaní SQL dotazov (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, 'nebol modifikovaný jeden záznam');
finally
mInputParams.Free;
end;
..
Príklad použitia parametrov pri získavaní dát z databáza (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 modifikácii dát alebo došlo k chybnému prečítaniu');
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 dohľ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 Hľadaný variabilný symbol
// ADocTypes TStrings do ktorých funkcia vracia typ dohľadaných dokladov
// AOIDS TStrings do ktorých funkcia vracia OID dohľadaných dokladov
// AFlag Príznak ako dohľadávať podľa sumy a zaplatenosti
// -1 - Vrátiť všetko vyhovujúce podľa VS bez ohľadu na stav zaplatenia a sumu - ignoruje nastavenie parametrov v agende Firemné údaje
// 0 - Vrátiť vyhovujúce podľa VS, sumy a stavu zaplatenia s ohľadom na nastavenie parametrov v agende Firemné údaje
// AFlagForOneFound Príznak ako dohľadávať podľa sumy
// 0 - Musí vyhovovať len VS
// 1 - Musí vyhovovať VS aj sumaa mena plateného dokladu
// AAmount Suma - keď je 0, neberie sa pri vyhľadávaní na sumu do úvahy
// ACurrency_ID Mena
// AFirm_ID Firma - ak je vyplnené, pri vyhľadávaní sa berie do úvahy aj firma na doklade
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 StringListoch mDocTypes a mOIDS sú vrátené identifikácie dohľadaných dokladov (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
...
// Pripojenie dokladu do ručného párovania
// Tu pripájame existujúcu príjemku do párovacej skupiny faktúry prijatej
mSourceGroup_ID := TNxCustomAccountedDocument(mReceiptCard).GetSourceGroup;
TNxCustomAccountedDocument(mReceivedInvoice].AttachSourceGroup(mSourceGroup_ID);
mReceivedInvoice.Save; // !!! POZOR - Pripojenie sa prejaví až po Save objektu
...
// Odpojenie dokladu z ručného párovania
// Tu odpájame už pripojenú príjemku z párovacej skupiny faktúry prijatej
mSourceGroup_ID := TNxCustomAccountedDocument(mReceiptCard).GetSourceGroup;
TNxCustomAccountedDocument(mReceivedInvoice].RemoveSourceGroup(mSourceGroup_ID);
mReceivedInvoice.Save; // !!! POZOR - Odpojenie sa prejaví až po Save objektu
...
// Zistenie, či je dokument pripojený v ručnom párovaní
// Tu zisťujeme, či je príjemka pripojená do párovacej skupiny faktúry prijatej
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:
{
Pridanie riadku 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 sa o zmene dozvedel hlavičkový dataset
mDataset.RefreshAndRestoreLastSelectedItem;
mDataSet.EnableControls;
end;
end;
{
Vyvoláva sa po vytvorení inštancie formulára.
}
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
mMAction: TMultiAction;
begin
// Vytvoríme novú jednoduchú akciu
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Pridanie riadku';
mAction.Hint := 'Pridanie riadku a aktualizácia 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 ako pridaním objektov, tak aj celých nových skupín objektov:
{
Umožňuje rozšíriť zoznam objektov na záložke X-väzby, a to ako pridaním objektov, tak aj celých nových skupín objektov. Odporúčame pridávať objekty na obe strany, teda ako na zdrojový, tak cieľový objekt, potom používateľ uvidí napr. na objekte typu A objekty typu B a tiež naopak z B uvidí A.
Ukážka skriptu rozširujúceho X-väzby business objektu Sklad o skupinu Vydané ponuky a v nej všetky záznamy s poradovým číslom 1.
A ďalej o skupinu Skladové karty so záznamami s kódom začínajúcim 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 + '=' + 'Vydané ponuky');
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;
// Možno použiť aj objekty definovateľných číselníkov
//AGroups.Add('1NPNI4M2JIVOFBV23CHPHFPN5W=Eshop - Číselníkové hodnoty vlastnosti skladovej karty');
//AObjects.Add('1NPNI4M2JIVOFBV23CHPHFPN5W=1000000101');
// Ukážka prázdnej skupiny
//AGroups.Add(Class_IssuedOrder + '=Prijaté objednávky');
//AGroups.Add(Class_Division + '=Strediská');
end;
begin
end.
2. Ukážkový skript, ktorý umožňuje upraviť poznámku zobrazovanú vo vizuále na záložke X-väzby.:
{
Ukážka skriptu pre business objekt Skladová karta. Na všetkých X-väzbách skladových kariet pridá do poznámky EAN. V X-väzbách agendy Sklady navyše ešte pridáva množstvo podľa 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ážka časti skriptu na vytvorenie X-väzby v tabuľke UserXLink medzi záznamami agend Objednávky prijaté -> Prijatá pošta. Záznamy sú označené ako 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, pre ktorý sa háčik vyvolal
//AParams
//Parametre, s ktorými sa číselník zavolal. AParams typicky pripravuje CompleteRollValidateParams háčik na BO. AParams sú iba na čítanie.
//ADSQL
//Prostredník na úpravu SQL.
//AKind
//Informácia, z akej časti číselníka sa háčik vyvolal
//sskPage - pri získaní stránky s dátami
//sskWhisperer - pri zobrazení našepkávača
//sskExists - Služba číselníka - LookUp
//sskOIDByPart - Služba číselníka - FindPart
//sskOID - Služba číselníka - CheckOnly
//sskAllID - Služba číselníka - GetIds
//sskOIDByPrefill - Služba číselníka - Prefill
//sskCorrectSelected - Služba číselníka - 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 podmienku
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 nerozmnož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ť vykonania dopytu.
Jadro ho používa napr. pre FULLTEXT.
Skúste použiť, ak 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;
Poddotaz 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
// Doplnenie identifikácie položky, pre ktorú je číselník používaný, a tým umožnenie v háčiku OnSelectSQL_Hook číselníka zohľadnenie daného miesta.
if AFieldCode = Self.GetFieldCode('StoreCard_ID') then begin
AParams.GetOrCreateParam(dtString, 'ObjPri_StoreCard_ID');
// Navyše ukážka odovzdania hodnoty iného poľa, ktoré ovplyvní výsledné obmedzenie.
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
// Príklad len jednoduchých kariet, ktorých názov sa začína 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
// Príklad len kariet so sériovými číslami
ADSQL.Where.Add('A.Category = 1');
if AParams.ParamExist('ObjPri_X_StoreCard_ID_FromDefForm') then begin
// Obmedzenie len ak je pole editované z definovateľného formulára. Tzn. obmedzenie na názov nebude použité pre tvorbu napr. 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ť:
// Vylúčenie niektorých záznamov
//mParams.GetOrCreateParam(dtString, '_Excluded').AsString := '1J50000101;M100000101';
// Obmedzenie len na zoznam povolených záznamov
//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'; // Otvorenie na konkrétnom zázname. '' - bez určenia
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čenie vybraných záznamov
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;
//zadanie ičo na otestovanie
mOrgIdentNumber := InputBox('IČO na vyhľadanie firmy', 'Zadajte IČO:', '00000000');
//naplnenie 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);
//naplnenie firmy z verejnej databázy (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 behu.
Po stlačení tlačidla Vyvolať výnimku a potvrdení dialógu, kde možno zmeniť text výnimky sa odošle do Abry požiadavka na budúcu výnimku. Abra pri najbližšom vykonaní 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 možno na základe logiky v business objekte (objednávky prijaté) - po nastavení radu dokladu - dosiahnuť zapnutie/vypnutie riadkovej zľavy a následne zobrazenie/skrytie stĺpcov v gride riadkov tej objednávky:
//v BO po vyplnení určitého radu chceme, aby sa aktivovala zľava
procedure AfterSetFieldValue_Hook(Self: TNxCustomBusinessObject; AFieldCode: Integer; AValue: TNxParameter; AOriginalValue: TNxParameter);
begin
if AFieldCode = 11000 then //rad dokladov
begin
if AValue.AsString = '1OU0000101' then //uvedená hodnota môže reprezentovať napr. rad"OP" - toto vyplynie z nejakej x položky alebo nejako inak
begin
Self.SetFieldValueAsBoolean('IsRowDiscount', True)
end
else
begin
Self.SetFieldValueAsBoolean('IsRowDiscount', False)
end
end;
end;
//vo vizuále budeme potrebovať globálnu premennú
var
fSite: TSiteForm;
//vo vizuále vytvoríme procedúru na spracovanie udalosti zmeny na rade dokladu
procedure InitSite_Hook(Self: TSiteForm);
var
mpnDocQueue_ID: TNxGeneralRollMovablePanel;
begin
mpnDocQueue_ID := TNxGeneralRollMovablePanel(Self.FindComponent('mpnDocQueue_ID'));
mpnDocQueue_ID.onInEditAdmit := @DQEditAdmit; //Vytvorenie procedúry na spracovanie udalosti zadania toho radu dokladov
fSite := Self;
end;
//a v rámci tej udalosti si vo vizuále nastavíme, čo potrebujeme - tu je to zobrazenie príp. skrytie stĺpcov v gride riadkov
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
// otvoríme stream s existujúcim PDF súborom
mPDFStream := TFileStream.Create(APDFFileName, fmOpenReadWrite);
try
// pomocou streamu načítame PDF do objektu na prácu s PDF
mPDFDocument.Open(mPDFStream);
// do PDF pridáme prílohu
i := mPDFDocument.AddAttachedFile;
mPDFAttachment := mPDFDocument.AttachedFiles[i];
// vložíme novú prílohu načítaním z disku
mPDFAttachmentStream := TFileStream.Create(AAddFileName, fmOpenRead);
try
// načítame obsah prílohy zo súboru
mPDFAttachment.LoadFromStream(mPDFAttachmentStream);
finally
mPDFAttachmentStream.Free;
end;
// doplníme ďalšie informácie k prílohe
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 zmeny do súboru 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
// vytvoríme objekt pre prácu s PDF
mPDFDocument := TPDFDocument.Create(nil);
try
// otvoríme stream s existujúcim PDF súborom
mPDFStream := TFileStream.Create(APDFFileName, fmOpenReadWrite);
try
// pomocou streamu načítame PDF do objektu pre prácu s PDF
mPDFDocument.Open(mPDFStream);
// prechádzame všetky prílohy vložené do PDF a uložíme ich na disk
for i := 0 to mPDFDocument.AttachedFileCount - 1 do
begin
mPDFAttachment := mPDFDocument.AttachedFiles[i];
// do StringListu si uložíme prípadné informácie o súboroch
mFilesInformation.Add('Informácia o prílohe č. ' + 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
// prílohu z PDF uložíme cez stream na disk
mPDFAttachment.SaveToStream(mPDFAttachmentStream);
finally
mPDFAttachmentStream.Free;
end;
end;
mPDFDocument.Close(False);
//Uložíme informácie o extrahovaných súboroch do rovnakého adresára kam sa súbory 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 skriptovací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 tejto ukážke preberáme len z výrobných príkazov
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 vytváraná z výrobného príkazu
begin
try
//tu údaje prevezmeme
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 skriptovania vytvoriť a odoslať e-mail v aplikácii 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 pri získaní inštancie Outlooku', Sender.Site)
else begin
mItem := mOutlook.CreateItem(0);
mItem.To := 'adresa1@firma.cz'; //adresa príjemcu
mItem.CC := 'adresa2@firma.cz; adresa3@firma.cz'; //adresy príjemcov kópie
mItem.Subject := 'Predmet e-mailu';
mItem.Body := 'Telo e-mailu';
mItem.Attachments.Add('C:\Users\jmeno\priloha.pdf'); //pripojenie súboru
//zobrazenie okna Outlooku s vytvoreným e-mailom
mItem.Display; //prípadne .Send na odoslanie
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. K vytváraniu 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 možno použiť len v rámci jedného vlákna. Hodnotu parametra nastavenú v jednom vlákne tak nemožno 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 možno pristúpiť ku GUI (napríklad zobraziť formulár) iba, ak je kód vyvolaný z hlavného vlákna. Ak k prístupu ku GUI dôjde z iného vlákna, celá aplikácia môže zamrznúť.
const
cParSelectedDivisionID = 'SelectedDivisionID'; //Názov parametra, ktorého hodnota sa ukladá do GlobThreadParams
//Vyvoláva sa pri tvorbe kontextu z klienta na aplikačný server (nielen pri prihlásení používateľa).
procedure AfterLogon_Hook(AContext: TNxContext);
begin
if NxIsMainThread and //Kód je volaný z hlavného vlákna, v ktorom je možné zobraziť formulár
(GetSelectedDivisionID = '') and //Stredisko ešte nebolo vybrané
Application.NxIsInteractive then //Ide o GUI aplikáciu, ktorá umožňuje zobrazenie okien
begin
SetSelectedDivisionID(SelectDivisionID(AContext));
end;
end;
//Vyvolá výber hodnoty z číselníka stredísk.
function SelectDivisionID(AContext: TNxContext): String;
var
mRollParams: TNxParameters;
begin
mRollParams := TNxParameters.Create;
try
mRollParams.GetOrCreateParam(dtString, '_ID').AsString := ''; // Otvorenie na konkrétnom zázname. '' - bez určenia
if NxShowRoll(AContext, Roll_Divisions, mRollParams, 0, '', nil) then
begin
Result := mRollParams.ParamByName('_ID').AsString;
end else
Result := SelectDivisionID(AContext); //Aby sme prinútili používateľa nejaké stredisko vybrať
finally
mRollParams.Free;
end;
end;
//Uloží hodnotu do parametra 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;
//Zistí hodnotu parametra SelectedDivisionID.
//Táto metóda správne vráti hodnotu parametra bez ohľadu na to, z ktorého vlákna je vyvolaná.
//Napríklad ju teda možno použiť v definovateľnom paneli.
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 na 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čítanie BO, v tomto prípade skladu
mStore.Load('3200000101');
// získanie jeho používateľských parametrov
mStore.GetUserParameters(mUserParams);
// vypísanie hodnoty parametra s kódom "param01" zo skupiny parametrov s kódom "ParamGroup01"
ShowMessage(mUserParams.AsList.ParamByName('ParamGroup01').AsList.ParamByName('UserParameters').AsList.ParamByName('param01').AsList.ParamByName('ParamValue').AsString);
// načítanie BO hodnoty parametra
mUserParamValue.Load(mUserParams.AsList.ParamByName('ParamGroup01').AsList.ParamByName('UserParameters').AsList.ParamByName('param01').AsList.ParamByName('ParamValue_ID').AsString);
// zmena hodnoty parametra
mUserParamValue.SetFieldValueAsString('ParamValue', 'Nová hodnota');
mUserParamValue.Save;
// vyprázdnenie cache parametrov pre daný BO
mStore.ClearUserParametersCache;
// vypísanie zmenenej 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'] := 'príliš žlťuč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'] := 'príliš žlťuč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 vytvorí nasledujúci JSON:
{
"desetinne_cislo": 8282.12,
"retezec": "príliš žlťučký koníček",
"subjson": {
"desetinne_cislo": 8282.12,
"retezec": "príliš žlťuč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íponou XLS. Takýto súbor následne vie otvoriť ms excel a možno pracovať s hodnotami.
Príklad použitia:
// Uloženie exportu do súboru
procedure GenerateEpxportTrialBalance(const AOS: TNxCustomObjectSpace; const AFrom, ATo: TDateTime; const APath: string);
const
cExportTrialBalanceID = 'P300000001'; // Identifikátor exportu Účtovné exporty - Export predvahy do MS-Excel
var
mFile, mDynSourceID: string;
mContext: TNxContext;
mParameter, mCondParameter, mValuesParameter: TNxParameter;
mConditions: TNxParameters;
begin
mFile := APath + '\Predvaha_' + 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
// Nastavenie dátumu od do podmienky Dátum účtovania
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 := 'Spustenie exportu predvahy';
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ého zápisu do logu, ktorý sa konfiguruje pomocou štandardného nastavenia cez nexus.cfg. Nastavením možno jednoducho určiť, do akej miery bude logovanie zo skriptovania detailné, pomocou určenia hodnoty úrovne (Level).
Na 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
# Na zápis chýb do logu nastavíme Level=2, pre zápis varovaní hodnotu Level=3 a pre detailný výpis Level=5
Level=5
Uvedená cesta LogsDirectory musí smerovať na sieťové úložisko, kam majú klienti prístup na zápis.
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
// Úrovne závažnosti sú definované výčtom TNxLogLevel = (logSystem,logCritical,logError,logWarning,logNotice,logInfo,logDebug)
// Najvyššia úroveň závažnosti logSystem hodnota 0, v skritpovaní nepoužívame.
// NxScriptingLog.WriteEvent(logError, 'Text systémovej chyby');
// Úroveň vznikla chyba Level=2
NxScriptingLog.WriteEvent(logError, 'Text vznikla chyby');
// Úroveň varovania Level=3
NxScriptingLog.WriteEvent(logWarning, 'Text varovania');
// Úroveň podrobného výpisu informácií Level=5
NxScriptingLog.WriteEvent(logInfo, 'Informatívny text');
NxScriptingLog.EnterSection('Logovanie bloku');
try
try
Sleep(1000);
RaiseException('Zachytená očakávaná výnimka');
except
NxScriptingLog.WriteEvent(logWarning, ExceptionMessage);
end;
finally
NxScriptingLog.LeaveSection('Logovanie bloku');
end;
end;
end;
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Zalogovanie 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 varovania
26.03.2024 22:03:43.483 [5] 00001A68 (Scripting) Informatívny text
26.03.2024 22:03:43.483 [4] 00001A68 (Scripting) Logovanie 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čakávaná výnimka
scripting callstack:
ExampleUseNxScriptingLog (): .RaiseException (40:23)
RunWriteToLog (ExampleUseNxScriptingLog.Faktúry prijaté (Agenda)): ExampleUseNxScriptingLog (16:27)
RunWriteToLog (14:1)
26.03.2024 22:03:44.501 [5] 00001A68 (Scripting)<-- (Logovanie bloku)
Príklad demonštruje vytvorenie XML dokumentu s digitálnym podpisom a jeho vloženie do SOAP obálky na účely 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 dialóg s výberom podpisového certifikátu a uložíme si jeho hash a miesto uloženia
mCertHash:= SelectCertificateDlg(mContext, mCertStore, mSite);
//vygenerujeme si GUID odosielanej správy
mMsgGUID:= NxTrim(LowerCase(GUIDToString(CFxGuid.CreateNew())),'{}');
mXML:= TNxScriptingXMLWrapper.Create;
try
//vytvoríme správu na podpísanie
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');
//urobíme XML kanonickým a podpíšeme ho (kanonizácia upraví xml do konzistentnej štandardizovanej formy)
mXML.MakeXMLCannonical(0, false);
mXML.SignXML(mCertHash, mCertStore, 1, mContext, 'ds');
//vytvorí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');
//podpísanú XML sprá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 v skriptovaní 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, keď systém prestane fungovať, pretože dôjde k čiastočnej likvidácii pôvodného ObjectSpace, ktorý už na ď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 súvisieť, napríklad objekt vytvorený prostredníctvom metódy CreateDocumentDriver.
try
mOLE := mContext.GetAbraOLEApplication();
mOleDoc := mOLE.CreateDocumentDriver();
...
finally
//Tieto riadky zaistia uvoľnenie OLE a sú nevyhnutné pre ďalšiu správnu funkciu 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. Ak to tak nie je, jednotlivé riadky zo súboru sú importované ako samostatné doklady.
Pre prípad, keď sú v importných dátach iba riadkové položky viac než 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áva sa po nastavení importného dokumentu pred spustením parsingu - umožňuje zmeniť 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áva sa po nastavení importného dokumentu pred spustením parsingu - umožňuje zmeniť 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čítavala dynamicky (napríklad do stĺpca cez NxSQLSelect), ale aby bola perzistentná. Podľa danej položky je možné aj radiť zoznam, prípadne hodnotu použiť na ď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 vykonania:
Na objekte skladovej karty vytvoríme definovateľnú položku X_Prodej365 typu Číslo.
Položku X_Prodej365 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 pre 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“.
Úspech – určuje, či úloha prebehla v poriadku
LogInfoStr – naplánovanej úlohe môžeme vrátiť dodatočné informácia 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ískanie dát cez SQL dotaz mInputParams.Clear; mInputParams.NewFromDataType(dtFloat, 'FromDate').AsFloat := Date - 365; ObjectSpace.SQLSelect2(cStoreCards, mStoreCardsData, mInputParams); // Sekvenčne - modifikácia položky X_Prodej365 // A. Zmena cez SQL Update - priama DB zmena bez zápisu sledovanie zmien - obchádza business logiku // B. Zmena cez business logiku - zmeny sú viditeľné v sledovanie zmien ak je nastavené na triede mStoreCardsData.First; while not mStoreCardsData.Eof do begin mStoreCardID := mStoreCardsData.Fields.FieldByName('ID').AsString; mDeliveredQuantity := mStoreCardsData.Fields.FieldByName('DeliveredQuantity').AsFloat; // A. Zmena cez SQL Update mInputParams.Clear; mInputParams.NewFromDataType(dtString, 'ID').AsString := mStoreCardID; mInputParams.NewFromDataType(dtFloat, 'X_Prodej365').AsFloat := 666; ObjectSpace.SQLExecute(cUpdateStoreCard, mInputParams); // ---A. // alebo // B. Zmena cez 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 predajov 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_Prodej365. Tu máme na výber z dvoch možností:
A. Zmena cez SQL Update - priama DB zmena bez zápisu sledovanie zmien - obchádza business logiku
B. Zmena cez business logiku - zmeny hodnôt sú viditeľné v sledovanie zmien (ak je nastavené na danej triede).
(v ukážkovom skripte teda ponechať len jednu z možností)
Založíme novú naplánovanú úlohu typu Skript.
Vykonanie naplánovanej úlohy vykoná prepočet predajov a hodnoty zapíše do položky X_Prodej365. V číselníku skladových kariet po občerstvení už máme vypočítané hodnoty predajov za posledných 365 dní. Podľa položky X_Prodej365 možno zoznam aj radiť 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ť vystavené 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á sa počas načítania záložiek pre náhľad prí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 := 'Predaje';
mTabData.GetOrCreateParam(dtString, 'ID').AsString := ASourceObject.OID;
AParams.AsList.Add(mTabData);
end;
end;
{
Vyvolá sa pri kliknutí na záložku bez dát vytvorenú skriptom.
}
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 := 'Stredisko';
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, 'Stredisko').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] := 'Stredisko';
mTableColumns[1] := 'Q14';
mTableColumns[2] := 'Q30';
mTableChart := CFxGoogleCharts.RenderTableChart('tablechart_Sales', 'Sales', mTableColumns, mTableParams);
mPieChart := CFxGoogleCharts.RenderPieChart('piechart_Sales', 'Sales', 'Strediská', 'Počet predaných kusov', 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('Graf predajov');;
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čítava 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 Fotodokumentace, v ktorom sú uložené obrázky, skript z týchto obrázkov vytvorí 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', '') + '\Fotodokumentácia';
if DirectoryExists(mPath) and (mPath <> '') then FillGaleryTabFromUNCFiles('Fotodokumentácia');
end;
Class_ServicedObject:
begin
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '');
if DirectoryExists(mPath) and (mPath <> '') then FillParametersFromUNCFiles;
mPath := NxEvalObjectExprAsStringDef(ASourceObject, 'X_Folder', '') + '\Fotodokumentácia';
if DirectoryExists(mPath) and (mPath <> '') then FillGaleryTabFromUNCFiles('Fotodokumentácia');
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 + '\Fotodokumentácia';
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ískame access token k službe "firebase.messaging" cez OAuth 2.0 na základe údajov 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 := 'Dohľadať vo faktúrach';
mAction.Items.Add('Dohľadať vo vydaných faktúrach (skript)');
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 := 'Otvorené zo skriptovania rad FV nezaplatené pre 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 možno 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 Everifin, 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'' sa nenašiel.');
end;
if not (Sender is TOAuth2Wizard) then begin
RaiseException('Nekompatibilný typ odosielateľa.');
end;
mOS := TOAuth2Wizard(Sender).ObjectSpace;
mToken := ObtainAccessToken(mOS, mCode); // Získanie prístupového tokenu cez Web API
mTokenEncrypted := CFxCrypt.EncryptWithANSIKeyToBase64(cCryptoSecretKey, TEncoding.UTF8.GetBytes(mToken)); // Zašifrovanie tokenu
// Tu môže prísť uloženie 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 presmerovanie 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ára syté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 prijatá'; // Názov 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čítanie hlavičkových údajov: 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áme číslo]';
end;
// Načítanie riadkov 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 prijatá</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>Riadky:</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 od hodnôt objektu zliav. V tomto skripte je využitý háčik BeforeShowPOSCashDiscountParamsForm_Hook. Háčik je dostupný vo všetkých aplikačných moduloch kás. Háčik sa vyvolá pred zobrazením formulára na zadanie zľavy. V parametroch háčika je odovzdávaný objekt zliav na kase a vlastný formulár na zadanie zliav.
{
Vyvolá sa pred zobrazením formulára na zadanie zľavy.)
}
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 zľava kód: ' + ADiscount.GetFieldValueAsString('Code');
mDiscountKind := ADiscount.GetFieldValueAsInteger('DiscountKind');
{
0 - Akčná zľava
1 - Finančná na tovar automatická
2 - Finančná na riadok
3 - Finančná na tovar
4 - Finančná na doklad
5 - Percentuálna na doklad
6 - Automatická percentuálna na tovar na doklade
7 - Percentuálna riadková
8 - Percentuálna riadková na tovar
9 - Percentuálna...
}
case mDiscountKind of
0: mDiscountKindText := 'Akčná';
1..4: mDiscountKindText := 'Finančná';
5..9: mDiscountKindText := 'Percentuálna';
else
mDiscountKindText := 'Mimo rozsahu';
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
//Tlač reportu (Agenda Reporty)
//Nastavenie obmedzenia reportu za dátum, sklady a skladové karty
//Obmedzenie za Dátum
mCondParams := mConditions.NewFromDataType(dtList, 'Date').AsList;
mCondParams.NewFromDataType(dtInteger, 'UsedKind').AsInteger := ckRange;
mCondParamsValues := mCondParams.NewFromDataType(dtList, 'Values').AsList;
// Obmedzenie za dátum je síce typu ckRange, ale ako údaj pre obmedzenie (k dátumu)
// sa berie len hodnota "LOW"
mCondParamsValues.NewFromDataType(dtFloat, '{:LOW}').AsFloat := 43449; // 15.12.2018
//mCondParamsValues.NewFromDataType(dtFloat, '{:HIGH}').AsFloat := Date; // dnes
// Obmedzenie za sklady (výber zoznamom - reťazec reprezentujúci zoznam ID - oddelené Entrom)
mCondParams := mConditions.NewFromDataType(dtList, 'Store_ID').AsList;
mCondParams.NewFromDataType(dtInteger, 'UsedKind').AsInteger := ckList;
mCondParams.NewFromDataType(dtString, 'ValueList').AsString := '2100000101'#13#10'3500000101';
// Obmedzenie za skladové karty (výber zoznamom - TStringList)
// StringList naplníme cez 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);
//Vykoná tlač priamo na tlačiareň podľa obmedzenia v mConditions - Report "Stav skladu k dátumu"
// tlač priamo na tlačiareň
NxPrintByConditions(mContext,
mConditions, // Obmedzenie pre report - podmienky
'DCGGWH4VRREL3FWD002BG34ZPK', // DynSQL - "Sklad - Stav k dátumu"
'V700000001', // Report ID - "Stav skladu k dátumu"
rtoPrint, // Typ operácie - Tlač na tlačiareň
pekARP, // Typ exportu
'KONICA MINOLTA C223', // Výstup - názov tlačiarne
''); // (slúži pre názov súboru v prípade tlače do súboru)
//Vykoná tlač do súboru PDF podľa obmedzenia v mConditions - Report "Stav skladu k dátumu"
// tlač do súboru PDF
NxPrintByConditions(mContext,
mConditions, // Obmedzenie pre report - podmienky
'DCGGWH4VRREL3FWD002BG34ZPK', // DynSQL - "Sklad - Stav k dátumu"
'V700000001', // Report ID - "Stav skladu k dátumu"
rtoFile, // Typ operácie - Tlač na tlačiareň
pekPDF, // Typ expByCoortu
'C:\ABRA', // Výstup - cesta k súboru (zadávať formát C:\ABRA\)
'Report Skladu - Stav k dátumu.pdf'); // Názov súboru
//Tlač tlačových zostáv podľa identifikácií objektov
//S rozšíreným nastavením tlačiarne (Collate, Duplex)
// Rozšírené nastavenie riskárne
mExtraParams := TNxParameters.Create;
try
// Zapneme kompletizáciu kópií
// true -> kópie budú tlačené 1,2,3 1,2,3
// false -> kópie budú tlačené 1,1 2,2 3,3
mExtraParams.GetOrCreateParam(dtBoolean, 'REPORT_COLLATE').AsBoolean := True;
// Zapneme obojstrannú tlač
mExtraParams.GetOrCreateParam(dtBoolean, 'REPORT_DUPLEX').AsBoolean := True;
//Vykoná tlač tlačovej zostavy “Zoznam tovaru (kód)” priamo na tlačiareň - obmedzenie je dané zoznamom ID skladových kariet
// tlač tlačovej zostavy
NxPrintByIDs(mContext,
mSCList, // Zoznam ID skladových kariet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Zoznam tovaru (kód)"
rtoPrint, // Typ operácie - Tlač na tlačiareň
pekARP, // Typ expByCoortu
'KONICA MINOLTA C223', // Výstup - cesta k súboru
'', // Názov súboru
2, // Počet kópií
false, // Emulovať kópie
mExtraParams); // Odovzdáva parametre pre tlač - Duplex alebo Collate
//Vykoná tlač tlačovej zostavy “Zoznam tovaru (kód)” do súboru PDF - obmedzenie je dané zoznamom ID skladových kariet
// tlač tlačovej zostavy.do súboru
NxPrintByIDs(mContext,
mSCList, // Zoznam ID skladových kariet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Zoznam tovaru (kód)"
rtoFile, // Typ operácie - Tlač na tlačiareň
pekPDF, // Typ expByCoortu
'C:\ABRA', // Výstup - cesta k súboru (zadávať formát C:\ABRA\)
'Zoznam tovaru(kód).pdf', // Názov súboru
2); // Počet kópií
// Vykoná tlač tlačovej zostavy "Zoznam tovaru (kód)" do súboru PDF - obmedzenie je dané zoznamom ID skladových kariet
// Parameter tlačiarne pri tlači do súboru spôsobí, že sa vynúti prevzatie nastavenia papiera z tejto tlačiarne
CFxReportManager.PrintByIDs(mContext,
mSCList, // Zoznam ID skladových kariet
'OGQQA2C25JDL342N01C0CX3FCC', // DynSource - Skladové karty
'F300000001', // Report ID - "Zoznam tovaru (kód)"
rtoFile, // Typ operácie - Tlač na tlačiareň
pekPDF, // Typ exoortu
'C:\ABRA\test\develop', // Výstup - cesta k súboru
'Zoznam tovaru(kód)_printer_KM.pdf', // Názov súboru
1, false, nil, // Počet kópií + emulateCopies + AParams
'KONICA MINOLTA C223'); // Názov tlačiarne, z ktorej sa preberá nastavenie stránky/papiera
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í, namiesto 2 vytlačí 4, namiesto 3 vytlačí 9 atď.
Funkcia NxPrintByConditions umožňuje tlač Reportov (agenda Reporty) podľa zadaného obmedzenia, napríklad Stav skladu k dátumu , Predaný tovar podľa skladov, Obraty účtov atď.
Parameter určujúci názov tlačiarne Počet kópií a emulácia 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ázka skladovej karty do ľavej časti stromu kusovníka. Pri prechode položkami stromu sa v ľavej časti zobrazuje obrázok z danej skladovej karty. Ak 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áva sa po vykonaní inicializácie agendy/formulára. V tento okamih je už na formulári 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á sa pred zobrazením formulára pre zadanie zľavy.)
}
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íska z prvého stĺpca kód skladovej karty
mSQL:= 'Select ID '+
'From StoreCards '+
'Where Hidden = ''N'' and Code = '+QuotedStr(TVirtualStringTree(Sender).GetFocusedNodeTextByColumn(0));
mID:= TVirtualStringTree(Sender).Site.BaseObjectSpace.SQLSelectFirstAsString(mSQL,'');
// Obrázok uložený na skl. karte
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
// Ak položka nemá obrázok, načítame vopred pripravený obrázok no_image - to môže byť napríklad upozornenie, že neobsahuje obrázok alebo prázdny obrázok
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
// Zahadzujem chyby - prípadne vyviesť chybu pomocou RaiseException
// alebo do okna s obrázkom vypísať text chyby (ExceptionMessage)
end;
end;
null a existencie 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); // Nastavenie null hodnoty
mJSON.I['cislo_cele'] := 12345;
ShowMessage(mJSON.AsJson);
mO := mJSON.O['neexistuje'];
if not mO.Exists then // Test existencie parametra
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 na zastavenie kódu v ScriptDebuggeri
Nasledujúci príklad, ktorý do agendy Adresár firiem pridáva tlačidlo na hromadnú tvorbu firiem, slúži hlavne ako ukážka, ako použiť procedúru NxScriptDebuggerBreakPoint, ktorá, ak je k systému ABRA Gen pripojený nástroj ScriptDebugger, zastaví kód na vybranom mieste.
Vytvoríme nový balíček skriptov a na záložke projekt vytvoríme druh skriptu Knižnica a pomenujeme ho 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.
Ďalej vytvoríme druh skriptu Agenda a vyberieme Adresár firiem:
uses
'CreateObjects';
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Hromadné vytvorenie firiem';
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é vytvorenie firiem', 'Zadajte počet', '10'));
for I:=0 to mCount - 1 do
CreateFirm(mSite.BaseObjectSpace,'Generated firm ' + IntToStr(I));
end;
begin
end.
Ak je následne k systému ABRA Gen pripojený ScriptDebugger (viď daná kapitola), zastaví sa po spustení funkcie Hromadné vytvorenie firiem skript na danom breakpointe.
Nasledujúci príklad pridáva do agendy Skladové karty tlačidlo, ktoré po stlačení ukazuje použitie skriptovacích funkcií na správu bezpečného úložiska:
Vytvoríme nový balíček skriptov a na záložke projekt vytvoríme druh skriptu Agenda a pomenujeme ho SecureStore:
procedure Main(Sender : TObject);
var
mSite: TSiteForm;
mObjectSpace: TNxCustomObjectSpace;
mKeyValue: String;
begin
// Funkcie pre bezpečné úložisko sú na ObjectSpace
mSite := TComponent(Sender).Site;
mObjectSpace := mSite.BaseObjectSpace;
// Ak kľúč existuje, potom načítame hodnotu do premennej a zobrazíme ju
if mObjectSpace.ReadFromSecureStore('mujklic', mKeyValue) then
begin
// Vypísanie načítanej hodnoty
ShowMessage(mKeyValue);
// Týmto volaním kľúč opäť vymažeme
mObjectSpace.DeleteFromSecureStore('mujklic');
ShowMessage('Kľúč bol odstránený');
end else
begin
// Vytvorenie kľúča "mujklic" s hodnotou aktuálneho dátumu
// Po spustení tohto kódu si môžeme hodnotu kľúča zobraziť v nástroji AppServerProp.exe > Funkcia > Bezpečné úložisko.
mObjectSpace.WriteToSecureStore('mujklic', DateTimeToStr(Now));
ShowMessage('Kľúč bol založený a môžete si ho používateľsky zobraziť v nástroji AppServerProp.exe');
// Opätovným zápisom vykonáme prepísanie už uloženej hodnoty
mObjectSpace.WriteToSecureStore('mujklic', 'Opätovne zmenené: ' + DateTimeToStr(Now));
end;
// Ak chceme, aby hodnota kľúča bola uložená pre konkrétneho používateľa, musíme do názvu kľúča uložiť identifikátor používateľa
// Zoznam existujúcich zákazkových kľúčov nie je možné načítať. Je k dispozícii iba správcom v nástroji 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žisko';
mAction.Category := 'tabList,tabDetail';
mAction.OnExecute := @Main;
end;
begin
end.
Ďalšie spôsoby správy bezpečného úložiska (Web API a AppServerProp) viď kap. Bezpečnosť dát.
Je pripravený nový skriptovací háčik MassInvoicing_InputGrouping_Hook. Háčik je dostupný v aplikačnom module "Systémové udalosti". 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ú zoskupovať vytvárané doklady faktúr vydaných (FV). V parametri AGroupList možno upraviť zoznam názvov položiek business objektu hlavičiek OP pre zoskupovanie FV.
Odovzdaný zoznam je už predvyplnený typickými položkami pre zoskupovanie: Currency_ID, TradeType, PricesWithVAT, VATDocument, OnlyWholeOrder.
Zoznam položiek možno v skriptovaní ľ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;
Nasledujúci príklad ukazuje, ako možno do agendy definovateľných položiek pridať tlačidlo, ktoré vykoná synchronizáciu definovateľných položiek naprieč vopred definovanými databázovými spojeniami. Skript využíva rozhranie Web API a jeho endpoint /installsets/distribution, ktorý umožňuje jednoduchý export a import rôznych systémových nastavení. Podrobnejší popis tohto API endpointu a jeho možností nájdete v kapitole PŘÍKLAD 22 - Synchronizace definic mezi spojeními.
Skript najprv z aktuálneho spojenia vyexportuje definovateľné položky (pomocou GET požiadavky na API), a následne ich naimportuje do ostatných definovaných spojení (pomocou POST požiadaviek).
Druh skriptu: Agenda - Definovateľné 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
Pri volaní SQL dotazu zo skriptu môže dôjsť k chybe "Bola prekročená max. veľkosť alokovanej pamäte pre riadok datasetu". Tento problém sa týka všetkých databázových platforiem (MSSQL, Oracle, Firebird) a dochádza k nemu, ak dotaz vracia viac stĺpcov, ktorých obsah je upravený funkciami. Či už ide o funkciu FORMAT na MSSQL, TO_CHAR na Oracle alebo zreťazenie pomocou + či ||, databázový server často implicitne priradí výslednému stĺpcu maximálny textový dátový typ (napr. NVARCHAR(4000)). Aj keď sú skutočné dáta krátke, súčet maximálnych dĺžok všetkých stĺpcov v jednom riadku môže prekročiť limit datasetu v aplikácii (typicky 65 536 bajtov).
Chybná konštrukcia
Nasledujúci dopyt pripravuje dáta pre tlačový výstup. Formátuje niekoľko číselných a textových polí. Každý z týchto upravených stĺpcov je databázou vrátený s veľkým implicitným dátovým typom, čo vo výsledku spôsobí pretečenie pamäte pre riadok.
SELECT
'FA-' + OrdNumber AS InvoiceID,
UPPER(FirmName) AS CustomerName,
REPLACE(FORMAT(TotalAmount, 'N2'), ',', '.') + ' CZK' AS FormattedTotal,
REPLACE(FORMAT(TotalVAT, 'N2'), ',', '.') + ' CZK' AS FormattedVAT,
'Splatnosť: ' + FORMAT(DueDate, 'dd.MM.yyyy') AS DueDateInfo
FROM
IssuedInvoices
WHERE
IsStorno = 0
Dôsledky
Zatiaľ čo dopyt sa v nástrojoch na správu databázy (napr. SQL Management Studio) vykoná bez problémov, pri spustení zo skriptu v aplikácii zlyhá s chybou o prekročení veľkosti riadka. Dôvodom je, že skriptovacie prostredie alokuje pamäť podľa definície stĺpcov, nie podľa skutočnej dĺžky dát. Súčet teoretických dĺžok (napr. 5x NVARCHAR(4000)) ľahko prekročí povolený limit.
Optimálne riešenie
Riešením je explicitne definovať dátový typ a jeho veľkosť pre každý transformovaný stĺpec pomocou funkcie CAST. Tým zaistíme, že celková definovaná veľkosť riadka zostane v limite datasetu. Funkcia CAST je štandardnou súčasťou SQL a je dostupná vo všetkých podporovaných databázach.
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('Splatnosť: ' + FORMAT(DueDate, 'dd.MM.yyyy') AS VARCHAR(30)) AS DueDateInfo
FROM
IssuedInvoices
WHERE
IsStorno = 0
Ako ľahko zistiť, ktoré stĺpce spôsobujú problém? V nástroji na správu databázy možno dopyt dočasne upraviť tak, aby jeho výsledok vložil do novej tabuľky (napr. pomocou klauzuly INTO new_table na MSSQL). Následnou inšpekciou stĺpcov novo vytvorenej tabuľky možno odhaliť, aké dátové typy databáza automaticky priradila, a ktoré je teda potrebné obmedziť pomocou CAST.
Skriptovací systém bol rozšírený o podporu automatickej dokumentácie API funkcií, ktorú možno volať priamo z API. Dokumentácia je registrovaná priamo v balíčkoch skriptov spoločne s implementáciou API funkcií.
Dokumentačné procedúry majú názov s prefixom OPENAPIDOC_ nasledovaným názvom API funkcie. Hlavičku dokumentácie možno tiež automaticky vygenerovať pomocou akcie „vygenerovať hlavičku → volanie OpenAPI dokumentácie funkcie“, ktorá vytvorí základnú štruktúru pre definíciu dokumentácie.
Dokumentácia sa vytvára pomocou objektu ARoutesBuilder, ktorý umožňuje definovať:
- jednotlivé HTTP routy,
- HTTP metódy (GET, POST, PUT, DELETE),
- parametre (query, path, header),
- formát a obsah odpovedí,
- štruktúru a obsah tela požiadavky,
- príklady JSON dát,
- JSON schémy pre validáciu tela dotazu.
Príklad založenia dokumentácie s názvom 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('Získa zoznam záznamov')
.AddResponses
.Ok('Záznamy boli získané')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.Post('Založí záznam metódou POST')
.AddParams
.BooleanParam('Force', 'Parameter vynúti uloženie objektu aj napriek softvalidáciám', False, rplQuery)
.FinishParams
.AddRequestBody
.JSONContent(cSchema, cExample)
.FinishRequestBody
.AddResponses
.Created('Bol vytvorený nový záznam')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.FinishRoute
.AddRoute('/{id}')
.Put('Modifikuje záznam')
.AddParams
.StringParam('id', 'id záznamu', True, rplPath)
.BooleanParam('Force', 'Parameter vynúti uloženie objektu aj napriek softvalidáciám', False, rplQuery)
.FinishParams
.AddRequestBody
.JSONContent(cSchema, cExample)
.FinishRequestBody
.AddResponses
.Ok('Záznam bol modifikovaný')
.JSONContent(cSchema, cExample)
.FinishMethod
.FinishResponses
.FinishDefinition
.Delete('Zmaže záznam')
.AddParams
.StringParam('id', 'id záznamu', True, rplPath)
.FinishParams
.AddResponses
.NoContent('Záznam bol zmazaný')
.FinishMethod
.FinishResponses
.FinishDefinition
.FinishRoute;
end;
Príklad demonštruje manipuláciu s PDF dokumentmi za použitia metód triedy CFxPDF. Kód je navrhnutý na použitie v dokladovej agende (Faktúry vydané) a nadväzuje funkciu na tlačidlo.
Konkrétne ukazuje, ako:
- Dynamicky vygenerovať PDF z aktuálne vybraného dokladu pomocou CFxReportManager.PrintByIDsToBytes.
- Pripojiť k tomuto vygenerovanému PDF ďalší externý súbor PDF z disku (CFxPDF.MergeInStream), čím dôjde k spojeniu dokumentov do jedného celku.
- Vytvoriť vodoznak prekrytím všetkých stránok výsledného dokumentu ďalším PDF z disku (CFxPDF.Overlay).
- Zabezpečiť finálny dokument heslom a obmedziť práva na prácu so súborom (ponechané iba povolenie tlače) (CFxPDF.Secure).
Všetky metódy triedy CFxPDF sú dostupné vo variante na prácu so súbormi na disku (String) a s objektmi TMemoryStream.
Pre správnu funkčnosť je nutné mať skript zaradený pod Druh skriptu: Agenda a vybranú Triedu objektu: Faktúry vydané (IssuedInvoices). Kód automaticky získava ID aktuálne vybraného dokladu pomocou pretypovania 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ÍSKANIE ID AKTÍVNEJ FAKTÚRY
if (TDynSiteForm(mSite).CurrentObject = nil) then
begin
NxShowSimpleMessage('Najprv vyberte faktúru v zozname alebo detaile!', mSite);
Exit;
end;
mCurrentOID := TDynSiteForm(mSite).CurrentObject.OID;
mOS := mSite.BaseObjectSpace;
mContext := NxCreateContext(mOS);
// Cesty k súborom - UPRAVTE PODĽA POTREBY. Súbory na načítanie MUSIA existovať!
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álny zabezpečený výstup
try
mIDList := TStringList.Create;
try
mIDList.Add(mCurrentOID);
mInputStreams := TObjectList.Create(False);
try
mInputA := TMemoryStream.Create;
try
// 2. Vygenerovanie PDF z reportu do pamäte (mInputA)
// SEM VLOŽTE ID SVOJEJ DEFINÍCIE REPORTU A VARIANTU PRE FAKTÚRY!
mInputA.SetBytes(
CFxReportManager.PrintByIDsToBytes(
mContext,
mIDList,
'40SBPEINEFD13ACM03KIU0CLP4', // DataSource reportu (Definícia) z Tlačových zostáv
'W400000001', // ID reportu (Varianta) - aktualizujte podľa potreby
pekPDF)
);
mInputB := TMemoryStream.Create;
try
// 3. Načítanie prílohy z disku (POVINNÝ SÚBOR)
if not FileExists(mPDFtoAppendPath) then
RaiseException('Súbor s prílohou neexistuje: ' + mPDFtoAppendPath);
mInputB.LoadFromFile(mPDFtoAppendPath);
// 4. Spojenie (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) - ak existuje prekryvný súbor
if FileExists(mOverlayPath) then
begin
CFxPDF.Overlay(
mOutputPath,
mOverlayPath,
mOutputWithWatermarkPath,
True // Prekrytie všetkých stránok prvou stránkou Overlay PDF
);
end
else
begin
mOutputWithWatermarkPath := mOutputPath;
end;
// 6. Zabezpečenie (Secure)
CFxPDF.Secure(
mOutputWithWatermarkPath,
mOutputSecuredPath,
'', // OldPassword (necháme prázdne)
'', // UserPassword (heslo na otvorenie dokumentu)
'owner123', // OwnerPassword (heslo pre práva/zmenu práv)
False, // AllowAccessibility
False, // AllowExtract
False, // AllowAssemble
False, // AllowAnnotateForm
False, // AllowFormFill
False, // AllowModifyOther
True // Povoliť tlač (AAllowPrint)
);
NxShowSimpleMessage('Hotovo. Faktúra s prílohou uložená do: ' + mOutputSecuredPath, mSite);
finally
mIDList.Free;
end;
finally
mContext.Free;
end;
end;
// Inicializačný háčik - pridanie tlačidla do agendy
procedure FormCreate_Hook(Self: TSiteForm);
var
mAction: TBasicAction;
begin
mAction := Self.GetNewAction;
mAction.ShowControl := True;
mAction.ShowMenuItem := True;
mAction.Caption := 'Tlač Faktúry s prílohou (PDF)';
mAction.Category := 'tabList';
mAction.OnExecute := @RunPDFTest;
end;
begin
end.
Na úpravu adresátov, predmetu a tela e-mailu pri odosielaní tlačí je možné použiť skriptovací háčik Aplikačný modul: Systémové udalosti - BeforeSendReportByEmail_Hook, ktorý sa volá po dokončenom tlačení a pred vytvorením samotného e-mailu (v agende Odoslané e-maily alebo v externom klientovi pomocou EML/MAPI/OLE). Ak sú v tlačovej zostave zadané parametre spustenia EMAIL, EMAIL_CC, EMAIL_BCC, EMAIL_SUBJECT, EMAIL_BODY, do háčika sa v parametroch odovzdajú výstupné hodnoty týchto parametrov.
Nasledujúci príklad demonštruje, ako do tela e-mailu zapísať pri tlači jednej faktúry vydanej obsah danej faktúry a pri tlači viacerých faktúr výpis hlavičkových údajov pre jednotlivé faktúry.
Príklad použitia:
procedure BeforeSendReportByEmail_Hook(AContext: TNxContext; AReportID: string; AReportName: string;
ASourceCLSID: string; AOIDsSelection: TStrings; AConditionsParams: TNxParameters; var AEmail: string;
var AEmailCC: string; var AEmailBCC: string; var AEmailSubject: string; var AEmailBody: string);
var
mHtml: String;
mMemTableHeader, mMemTableRows: TMemTable;
mInputParams: TNxParameters;
mAmount, mRowAmount: Double;
mRowType, I: Integer;
mIDs, mID: String;
mText, mStoreCardName, mDisplayText, mDesc: String;
mInvoiceNum: String;
mDocQueueCode, mPeriodCode: String;
begin
if ASourceCLSID = Class_IssuedInvoice then
begin
// Vloženie vlastného tela e-mailu, ak nebolo žiadne vložené cez parametre spustenia
if AEmailBody = '' then
begin
mInputParams := TNxParameters.Create;
mMemTableHeader := TMemTable.Create(nil);
try
// Ak boli vytlačené dva a viac záznamov, vypíšeme informácie z ich hlavičiek
if AOIDsSelection.Count > 1 then
begin
// Načítanie hlavičkových údajov faktúr: Description, Amount, DQ.Code, OrdNumber, P.Code
mIDs := '(';
for I := 0 to AOIDsSelection.Count - 1 do
begin
mIDs := mIDs + '''' + AOIDsSelection[I] + ''',';
end;
mIDs := Copy(mIDs, 1, Length(mIDs) - 1) + ')';
AContext.SQLSelect2(
'SELECT II.LocalAmount, II.OrdNumber, II.Description, DQ.Code AS DocQueueCode, P.Code AS PeriodCode ' +
'FROM IssuedInvoices II ' +
'JOIN Periods P ON P.ID = II.Period_ID ' +
'JOIN DocQueues DQ ON DQ.ID = II.DocQueue_ID ' +
'WHERE II.ID IN '+ mIDs,
mMemTableHeader
);
// HTML výstup
mHtml :=
'<h4>Vážený zákazník,<br>v prílohe nájdete faktúry k Vašej objednávke:</h4>' +
'<ul>';
if not mMemTableHeader.IsEmpty then
begin
mMemTableHeader.First;
while not mMemTableHeader.Eof do
begin
mAmount := mMemTableHeader.FieldByName('LocalAmount').AsFloat;
mDocQueueCode := mMemTableHeader.FieldByName('DocQueueCode').AsString;
mPeriodCode := mMemTableHeader.FieldByName('PeriodCode').AsString;
mInvoiceNum := mDocQueueCode + '-' + IntToStr(mMemTableHeader.FieldByName('OrdNumber').AsInteger) + '/' + mPeriodCode;
mDesc := mMemTableHeader.FieldByName('Description').AsString;
if mDesc <> '' then
begin
mDesc := ', ' + mDesc;
end;
mHtml := mHtml + '<li>' + mInvoiceNum + ' (' + FormatFloat('#,##0.00 Kč', mAmount) + ')' + mDesc + '</li>';
mMemTableHeader.Next;
end;
end;
mHtml := mHtml + '</ul>';
end else
begin
mMemTableRows := TMemTable.Create(nil);
try
mInputParams.NewFromDataType(dtString, 'ID').AsString := AOIDsSelection[0];
// Načítanie hlavičkových údajov faktúry: Amount, DQ.Code, OrdNumber, P.Code
AContext.SQLSelect2(
'SELECT II.LocalAmount, II.OrdNumber, DQ.Code AS DocQueueCode, P.Code AS PeriodCode ' +
'FROM IssuedInvoices II ' +
'JOIN Periods P ON P.ID = II.Period_ID ' +
'JOIN DocQueues DQ ON DQ.ID = II.DocQueue_ID ' +
'WHERE II.ID = :ID',
mMemTableHeader,
mInputParams
);
if not mMemTableHeader.IsEmpty then
begin
mAmount := mMemTableHeader.FieldByName('LocalAmount').AsFloat;
mDocQueueCode := mMemTableHeader.FieldByName('DocQueueCode').AsString;
mPeriodCode := mMemTableHeader.FieldByName('PeriodCode').AsString;
mInvoiceNum := mDocQueueCode + '-' + IntToStr(mMemTableHeader.FieldByName('OrdNumber').AsInteger) + '/' + mPeriodCode;
AEmailSubject := AReportName + ' ' + mInvoiceNum;
end else
begin
mAmount := 0;
mInvoiceNum := '[neznáme číslo]';
end;
// HTML výstup
mHtml :=
'<h4>Vážený zákazník,<br> v prílohe nájdete faktúru ' + mInvoiceNum + ' k Vašej objednávke.</h4>' +
'<p><strong> Celková cena (lok.):</strong> ' + FormatFloat('#,##0.00 Kč', mAmount) + '</p>' +
'<h4>Riadky:</h4>' +
'<ul>';
// Načítanie riadkov faktúry
AContext.SQLSelect2(
'SELECT II2.RowType, II2.Text, II2.LocalTAmount, SC.Name ' +
'FROM IssuedInvoices2 II2 ' +
'LEFT JOIN STORECARDS SC ON SC.ID = II2.STORECARD_ID ' +
'WHERE II2.PARENT_ID = :ID',
mMemTableRows,
mInputParams
);
if not mMemTableHeader.IsEmpty then
begin
mMemTableRows.First;
while not mMemTableRows.Eof do
begin
mRowType := mMemTableRows.FieldByName('RowType').AsInteger;
mText := mMemTableRows.FieldByName('Text').AsString;
mStoreCardName := mMemTableRows.FieldByName('Name').AsString;
mRowAmount := mMemTableRows.FieldByName('LocalTAmount').AsFloat;
if mRowType = 3 then
mDisplayText := mStoreCardName
else
mDisplayText := mText;
mHtml := mHtml + '<li>' + mDisplayText + ', cena (lok.): ' + FormatFloat('#,##0.00 Kč', mRowAmount) + '</li>';
mMemTableRows.Next;
end;
end;
mHtml := mHtml + '</ul>';
finally
mMemTableRows.Free;
end;
end;
finally
mInputParams.Free;
mMemTableHeader.Free;
end;
AEmailBody := mHtml;
end;
end;
end;















