Print multiple reports on business central - dynamics-business-central

I need to use multiples Report.Print() in a code, but business central only prints the last requested print.
Using a dialog.confirm(), a page.runmodal() or a message() between the prints works, but I need the code to run automatically without user input.
Any ideas?
Ex.: Not working, only last one prints
codeunit 90101 Test
{
trigger OnRun()
var
salesInvoice: Record "Sales Invoice Header";
RecRef: RecordRef;
begin
salesInvoice.setfilter("No.", '103021');
RecRef.GetTable(salesInvoice);
Report.Print(1306, '', '', RecRef);
salesInvoice.Reset();
salesInvoice.setfilter("No.", '103022'); //only this one prints
RecRef.GetTable(salesInvoice);
Report.Print(1306, '', '', RecRef);
end;
}
Ex2.: working, prints both
codeunit 90101 Test
{
trigger OnRun()
var
salesInvoice: Record "Sales Invoice Header";
RecRef: RecordRef;
begin
salesInvoice.setfilter("No.", '103021');
RecRef.GetTable(salesInvoice);
Report.Print(1306, '', '', RecRef);
salesInvoice.Reset();
salesInvoice.setfilter("No.", '103022');
RecRef.GetTable(salesInvoice);
if Dialog.Confirm('hello') then;
Report.Print(1306, '', '', RecRef);
end;
}

I'm having the same issue.
I was just as confused as I kept thinking "but how does report selection work then?" but realized it always opens the request page.
I'm considering these 2 options:
Save as pdf and store it to a blob and print it using a .net service running on the application server. See this article for printing pdf's.
Create a non-recurring job queue entry to print each report.
This is worked out quite well for me:
...
CreatePrintingJob(Report::"Warehouse Shipment", WarehouseShipmentHeader.RecordId);
...
procedure CreatePrintingJob(_ReportID: Integer; _RecordToPrint: RecordId)
begin
CreatePrintingJob(_ReportID, '', _RecordToPrint);
end;
procedure CreatePrintingJob(_ReportID: Integer; _ReportParameters: Text; _RecordToPrint: RecordId)
var
jobQueueEntry: Record "Job Queue Entry";
begin
jobQueueEntry.Init();
jobQueueEntry."Object Type to Run" := jobQueueEntry."Object Type to Run"::Report;
jobQueueEntry."Object ID to Run" := _ReportID;
jobQueueEntry."Record ID to Process" := _RecordToPrint;
jobQueueEntry."Starting Time" := 000000T;
jobQueueEntry."Maximum No. of Attempts to Run" := 3;
jobQueueEntry."Report Output Type" := jobQueueEntry."Report Output Type"::Print;
jobQueueEntry.Insert(true);
jobQueueEntry.SetReportParameters(_ReportParameters);
jobQueueEntry.ScheduleTask();
end;

Related

How to see .w trigger is working for my query?

Just for the knowledge I just want to see how WRITE triggers execute for the query below. Is it possible to see them?.
FOR EACH Customer EXCLUSIVE-LOCK WHERE NAME = "Go Fishing Ltd":
ASSIGN Customer.Balance = 600.
END.
Add:
-clientlog path/to/log.log -logginglevel 4 -logentrytypes 4GLTrace
to your startup command.
This will create a log of all of the calls that your code makes.
For more information: https://knowledgebase.progress.com/articles/Knowledge/P9893
You can also use the LOG-MANAGER system handle within your code to dynamically control the logging at runtime:
https://docs.progress.com/bundle/openedge-abl-troubleshoot-applications/page/LOG-MANAGER-system-handle-attributes-and-methods.html
but for simple purposes like this it is easier to just add the startup parameters.
Watch the log-manager show the write trigger being executed on the sports2020 database:
def var clog as char no-undo.
def var lclog as longchar no-undo.
assign
clog = guid + '.log'.
log-manager:logfile-name = clog
log-manager:log-entry-types = '4gltrace:5,4glmessages'
.
for each Customer exclusive-lock where name = 'Go Fishing Ltd':
Customer.Balance = Customer.Balance + 1. // write trigger only fires when record changes
end.
log-manager:close-log().
copy-lob from file clog to lclog.
message string( lclog ).
https://abldojo.services.progress.com/?shareId=62978a833fb02369b25479f0
Relevant snippet from the output:
4GLTRACE Return from Main Block "Customer Customer" [sports2020trgs/wrcust.p]

Progress4gl how to lock yourself?

In an unit test, I need to verify that the program skip locked records when processing a table.
I have been unable to setup a locked records because the test can't lock itself which make a lot of sense.
Here is a sample of what I'm trying to achieve.
DEV VAR v_isCommitted AS LOGI NO-UNDO.
DEF VAR hl AS HANDLE NO-UNDO.
DEF BUFFER bufl FOR tablename.
hl = BUFFER bufl:HANDLE.
LOCKED_RECORDS:
DO TRANSACTION ON ERROR UNDO, LEAVE LOCKED_RECORDS:
/*Setup : Create record not committed yet*/
CREATE tablename.
ASSIGN tablename.fields = fieldsvalue.
/*ACT : Code I'm trying to test*/
/*...some code...*/
v_isCommitted = hl:FIND-BY-ROWID(ROWID(tablename), EXCLUSIVE-LOCK, NO-WAIT)
AND AVAILABLE(bufl)
AND NOT LOCKED(bufl).
/*...some code touching the record if it is commited...*/
/*ASSERT : program left new record tablename AS IS.*/
END.
The problem is that the record is available and not locked to the test because it was created by it.
Is there a way I could have the test lock a record from itself so the act part can actually skip the record like it was created by someone else?
Progress: 11.7.1
A session can not lock itself. So you will need to start a second session. For example:
/* code to set things up ... */
/* spawn a sub process to try to lock the record */
os-command silent value( substitute( '_progres -b -db &1 -p lockit.p -param "&2" && > logfile 2>&&1', dbname, "key" )).
In lockit.p use session:parameter to get the key for the record to test (or hard code it I suppose).
Or, as mentioned in the comments below:
/* locktest.p
*/
define variable lockStatus as character no-undo format "x(20)".
find first customer exclusive-lock.
input through value( "_progres /data/sports120/sports120 -b -p ./lockit.p" ).
repeat:
import unformatted lockStatus.
end.
display lockStatus.
and:
/* lockit.p
*/
find first customer exclusive-lock no-wait no-error.
if locked( customer ) then
put "locked".
else
put "not locked".
quit.

dbgrid looping to update data not working

so I have a DBGRID that has this infos :
I wanted to edit values inside that DBGRID. I provide a button with codes :
var ptlok, idp, tgl, after, kode, jlh, sql, sqla:string;
begin
while not dbgrid2.DataSource.DataSet.Eof do
begin
ptlok:=dbgrid2.DataSource.DataSet.Fields[0].AsString;
tgl:=dbgrid2.DataSource.DataSet.Fields[1].AsString;
after := stringreplace(tgl, '/', '-', [rfReplaceAll, rfIgnoreCase]);
kode:=dbgrid2.DataSource.DataSet.Fields[2].AsString;
jlh:=dbgrid2.DataSource.DataSet.Fields[3].AsString;
idp:=dbgrid2.DataSource.DataSet.Fields[4].AsString;
sql:='update kasir_opname_detail set kode='+quotedstr(kode)+',
jumlah='+quotedstr(jlh)+' where ptlokasi='+quotedstr(ptlok)+' and
tanggal=to_date('+quotedstr(after)+','+quotedstr('dd-mm-yyyy')+')'+' and
idpay='+quotedstr(idp)+'';
sqla:=sql;
zquery13.close;
zquery13.sql.clear;
zquery13.sql.add(sqla);
zquery13.execsql;
dbgrid2.DataSource.dataset.Next;
showmessage(sqla);
//end;
label19.Caption:=sqla;
end;
showmessage('Data Berhasil Disimpan!');
end;
However the data aren't saved as expected. it will only read last line of DBGRID then updates all data with values from last line of DBGRID.
is there something wrong with the code?
(prev it works with different dbgrid)
I am using delphi 7 and oracle as database. Thanks in advance.

Retrieve "Variant" data type from Enterprise Architect using DXL

How can I use the DXL OLE mechanism to fetch a diagram's modification time from Enterprise Architect 12?
Details:
I want to retrieve diagrams from EA and integrate them as OLE object into IBM Rational DOORS 9.5. This is already working. I intend to compare the modification dates of the EA diagram and the DOORS object before retrieving the diagram to decide if this operation is really needed.
Problem is, EA provides a diagram attribute EA.Diagram.ModifiedDate which returns the diagram's modification date as data type Variant. How can I handle this within DXL? The result parameter for oleGet() can be one of the types string|int|bool|char|OleAutoObj. No structured type (which would probably be DxlObject). Neither string nor int parameters contain any useful data after the call -- just null values.
Test code:
OleAutoObj eaRepository, eaProject, eaDiagram
OleAutoObj eaApp = oleGetAutoObject("EA.App")
OleAutoArgs autoArgs = create
string guid = "{729F140F-9DA4-4ff6-A9B2-75622AD1C22D}"
// connect to an existing EA instance
oleGet (eaApp, "Repository", eaRepository)
oleMethod (eaRepository, "GetProjectInterface", autoArgs, eaProject)
// call EA to a diagram which has this GUID
put(autoArgs, guid)
oleMethod(eaRepository, "GetDiagramByGuid", autoArgs, eaDiagram)
delete autoArgs
// access diagram attributes
string eaModifiedDate // DXL accepts [string|int|bool|char|OleAutoObj] only
oleGet(eaDiagram, "ModifiedDate", eaModifiedDate)
print "ModifiedDate = '" eaModifiedDate"'\n"
IBM's Support Team (commercial, available for paying customers) couldn't help and suggested to forward this question to the Service Team (for extra $s). Rather disappointing.
Try this one (just guessing :-)
OleAutoObj eaModifiedDate
oleGet(diagram, "ModifiedDate", eaModifiedDate)
if (null eaModifiedDate)
print "no eaModifiedDate\n"
else {
string diaDate
oleGet(eaModifiedDate, "Value", diaDate)
print "ModifiedDate = '" diaDate"'\n"
}
If that does not work then here comes the ultimate work around:
string err
string result
put(autoArgs, "SELECT ModifiedDate FROM t_diagram WHERE ea_guid = '" guid "'")
err = oleMethod (eaRepository, "SQLQuery", autoArgs, result)
if (!null err)
print "ERROR: " err "\n"
delete autoArgs
print "result= '" result"'\n"
This will return a XML formatted string (!) which contains the date in the format you like.
Edit 1: Turns out that EA's SQLQuery is buggy and returns just the date. Since Perl deals with OLE Variants in a similar way I found this code snippet:
my $dia = $rep->getdiagrambyguid("{63EFF3FA-0D5C-4986-AC0A-C723D2F755E3}");
my $value = $dia->ModifiedDate->Value;
print $value->Date( 'yyyy/MM/dd' );
print $value->Time( 'hh:mm:ss' );
So the ModifiedDate is an ole-object and has a Date and a Time method. This should work with DXL too.
Edit 2:Now here's the ulti-ultimate work around even shipping around the cliffs of EA's bug ocean:
my $dia = $rep->SQLQuery("SELECT Format (ModifiedDate, \"Short Time\") AS T FROM t_diagram");
This will format the date as time string. Works for EAP (aka Mickeysoft Access) according to this page. Other RDBMS likely have similar functions.
An update with a few corrections of my statements in comments.
I was wrong. It is indeed possible to receive structured data types using the OLE automation interface. As Thomas mentioned, the OleAutoObject is the proper DXL return type, oleGet() is the proper function. To access the returned object's attributes, it is required to be aware of the "real" data type and to have a look at the SDK documentation in order to know which class attributes are available since DXL is not aware of the data type.
However, in DXL, retrieving the attribute of a structured data type is a bit cumbersone. Example (intended to be appended to the inital post's code):
OleAutoObj diaLinksCollection
int count = -1
string buffer = ""
// access simple diagram attributes
err = oleGet(eaDiagram, "Version", buffer)
if (!null err) print "ERROR in Diagram.Version: " err "\n"
print "diagram version = " buffer "\n"
// access structured diagram attribute
err = oleGet(eaDiagram, "DiagramLinks", diaLinksCollection)
if (!null err) print "ERROR in Diagram.DiagramLinks: " err "\n"
err = oleGet(diaLinksCollection, "Count", count)
if (!null err) print "ERROR in Collection.Count: " err "\n"
print "count = " count "\n"
Thus, it is indeed possible to execute an SQL query, even if the repository is residing in a plain *.eap file, see Thomas' workaround.
As mentioned, the Variant data type can not be retrieved using oleGet() since this approach returns a NULL result.

Get words out of a file in Ada

I have a folder with files containing some text. I am trying to go through all the files one after the other, and count how many times we see every word in the text files.
I know how to open the file, but once I'm in the file I don't know how to read each word one after the other, and go to the next word.
If anyone has some ideas to guide me it'd be great.
Read the file a line at a time into a string using Get_Line, then break the line up into the individual words.
Here's one way of doing it, I needed some playtime with the containers.
Using streams is still the best solution for your problem, given the multiple files.
Text_Search.ads
Pragma Ada_2012;
With
Ada.Containers.Indefinite_Ordered_Maps;
Package Text_Search with Elaborate_Body is
Text : Constant String :=
ASCII.HT &
"We hold these truths to be self-evident, that all men are created " &
"equal, that they are endowed by their Creator with certain unalienable "&
"Rights, that among these are Life, Liberty and the pursuit of " &
"Happiness.--That to secure these rights, Governments are instituted " &
"among Men, deriving their just powers from the consent of the governed" &
", --That whenever any Form of Government becomes destructive of these " &
"ends, it is the Right of the People to alter or to abolish it, and to " &
"institute new Government, laying its foundation on such principles " &
"and organizing its powers in such form, as to them shall seem most " &
"likely to effect their Safety and Happiness. Prudence, indeed, will " &
"dictate that Governments long established should not be changed for " &
"light and transient causes; and accordingly all experience hath shewn, "&
"that mankind are more disposed to suffer, while evils are sufferable, " &
"than to right themselves by abolishing the forms to which they are " &
"accustomed. But when a long train of abuses and usurpations, pursuing " &
"invariably the same Object evinces a design to reduce them under " &
"absolute Despotism, it is their right, it is their duty, to throw off " &
"such Government, and to provide new Guards for their future security." &
"now the necessity which constrains them to alter their former Systems " &
"of Government. The history of the present King of Great Britain is a " &
"history of repeated injuries and usurpations, all having in direct " &
"object the establishment of an absolute Tyranny over these States. To " &
"prove this, let Facts be submitted to a candid world.";
Package Word_List is New Ada.Containers.Indefinite_Ordered_Maps(
Key_Type => String,
Element_Type => Positive
);
Function Create_Map( Words : String ) Return Word_List.Map;
Words : Word_List.map;
End Text_Search;
Text_Search.adb
Package Body Text_Search is
Function Create_Map( Words : String ) Return Word_List.Map is
Delimiters : Array (Character) of Boolean:=
('.' | ' ' | '-' | ',' | ';' | ASCII.HT => True, Others => False);
Index, Start, Stop : Positive := Words'First;
begin
Return Result : Word_List.Map do
Parse:
loop
Start:= Index;
-- Ignore initial delimeters.
while Delimiters(Words(Start)) loop
Start:= 1+Start;
end loop;
Stop:= Start;
while not Delimiters(Words(Stop)) loop
Stop:= 1+Stop;
end loop;
declare
-- Because we stop *on* a delimiter we mustn't include it.
Subtype R is Positive Range Start..Stop-1;
Substring : String renames Words(R);
begin
-- if it's there, increment; otherwise add it.
if Result.Contains( Substring ) then
Result(Substring):= 1 + Result(Substring);
else
Result.Include( Key => substring, New_Item => 1 );
end if;
end;
Index:= Stop + 1;
end loop parse;
exception
When Constraint_Error => null; -- we run until our index fails.
end return;
End Create_Map;
Begin
Words:= Create_Map( Words => Text );
End Text_Search;
Test.adb
Pragma Ada_2012;
Pragma Assertion_Policy( Check );
With
Text_Search,
Ada.Text_IO;
Procedure Test is
Procedure Print_Word( Item : Text_Search.Word_List.Cursor ) is
use Text_Search.Word_List;
Word : String renames Key(Item);
Word_Column : String(1..20) := (others => ' ');
begin
Word_Column(1..Word'Length+1):= Word & ':';
Ada.Text_IO.Put_Line( Word_Column & Positive'Image(Element(Item)) );
End Print_Word;
Begin
Text_Search.Words.Iterate( Print_Word'Access );
End Test;
Instead of going by individual words, you could read the file a line at a time into a string using Get_Line, and then use regular expressions: Regular Expressions in Ada?
If you're using Ada 2012 here's how I would recommend doing it:
With Ada.Containers.Indefinite_Ordered_Maps.
Instantiate a Map with String as key, and Positive as Key;
Grab the string; I'd use either a single string or stream-processing.
Break the input-text into words; this can be done on-the-fly if you use streams.
When you get a word (from #4) add it to your map if it doesn't exist, otherwise increment the element.
When you are finished, just run a For Element of WORD_MAP Loop that prints the string & count.
There's several ways you could handle strings in #3:
Perfectly sized via recursive function call [terminating on a non-word character or the end of input].
Unbounded_String
Vector (Positive, Character) -- append for valid characters, convert to array [string] and add to the map when an invalid-character [or the end of input] is encountered -- working variable.
Not Null Access String working variable.

Resources