The firt part of this series in here http://axwonders.blogspot.com/2011/02/save-microsoft-dynamics-ax-2009-report.html
In the prior article I created a job to save an Axapta report to PDF into a local drive ... C:\. In this article, the code from the last article is integrated within the Run() method of a class I created called SalesConfirmReportEmail.
The SalesConfirmReportEmail class gets executed when a a user wants to send and post an Order Acknowledgement on a Sales Order. I will not discuss how the code works from the moment the user clicks ok in the SalesEditLines form, but I will point out the path that the code follows:
So, the user opens the Sales Order form, finds a record, goes to Posting > Acknowledgement , does whatever he/she needs to do and clicks OK.
In my implementation, the user can choose the output for the order confirmation report. This is, in the DocDestination drop down list he/she can choose Preview, Print, and/or Email. In this case the user wants to print the report.
As soon as the user clicks OK, the code goes through the following:
- Classes\FormMenuButton\Clicked
- Classes\FormFunctionButtonCOntrol\Clicked
- Classes\SalesFormLetter\Main
- Classes\SalesFormLetter\MainOnServer
- Classes\SalesFormLetter\Run
- Classes\SalesFormLetter_Confirm\printJournal
- Data Dictionary\Tables\CustConfirmJour\Methods\printJournal
- Classes\SalesConfirmReportEmail\postingPrint
- Classes\SalesConfirmReportEmail\run
- Classes\SysInetMail\SendMailAttach
- Classes\SysINetOutlook\AddAttachment
void printJournal(SalesFormLetter salesFormLetter = null,
RecordSortedList journalList = null)
{
Args parameters = new Args();
MenuFunction salesConfirmMenu;
;
if(salesFormLetter && salesFormLetter.SalesParmUpdate().DocDestination == glPrintType::Printer)
{
salesConfirmMenu = new MenuFunction(menuItemoutputStr(SalesConfirmation), MenuItemType::Output);
parameters.caller(salesFormLetter);
if (journalList)
{
parameters.object(journalList);
}
else
{
parameters.record(this);
}
salesConfirmMenu.run(parameters);
}
else
{
//Calling my report confirmation class
//Notice that I'm passing the salesformletter class and journal list
SalesConfirmReportEmail::postingPrint(salesFormLetter,journalList);
}
}
The following is the code for the SalesConfirmReportEmail class. It is going to be long.
class SalesConfirmReportEmail
{
RecordSortedList journalList;
glPrintType printType;
PrintJobSettings printJobSetting;
FileName fileName;
FilePath filePath;
//Earias - 2/22/2011
SalesTable salesTable;
CustConfirmJour custConfirmJour;
DirPartyId dirPartyId;
SalesQuotationTable salesQuotationTable;
Filename prtFile;
Filepath prtFilePath;
Email email;
Email CCEmail;
Telefax fax;
SysINetMail_GLBTS mail;
str _mailBody;
}
Email CCEMail(SalesTable _salesTable)
{
Email _ccEmail;
EmplTable _emplTableOutside;
EmplTable _emplTableInside;
EmplId _emplIdInside;
;
_emplIdInside = EmplTable::userId2EmplId(_salesTable.SalesResponsible);
if (_salesTable.SalesTaker == _emplIdInside)
_ccEmail = Empltable::find(_salesTable.SalesTaker).email(); //outside salesperson set with emplId
else
{
_ccEmail = Empltable::find(_salesTable.SalesTaker).Email(); //outside salesperson set with emplId
if (!_ccEmail)
_ccEmail = Empltable::find(_emplIdInside).Email(); //inside salesperson set with UserId
else
_ccEmail += "; " + Empltable::find(_emplIdInside).Email(); //inside salesperson
}
return _ccEmail;
}
public FileName parmFileName(FileName _fileName = fileName)
{
;
fileName = _fileName;
return fileName;
}
glPrintType parmglPrintType(glPrintType _printType = printType)
{
;
printType = _printType;
return printType;
}
void parmJournalList(Common _common)
{
;
journalList = FormLetter::createJournalListCopy(_common);
}
Container parmPrintJobSettings(PrintJobSettings _printJobSetting = printJobSetting)
{
;
return printJobSetting.packPrintJobSettings();
}
void printJournal(CustConfirmJour _custConfirmJour)
{
Args parameters = new Args();
MenuFunction salesConfirmMenu;
;
salesConfirmMenu = new MenuFunction(menuItemoutputStr(SalesConfirmation),MenuItemType::Output);
parameters.caller(this);
parameters.record(_custConfirmJour);
salesConfirmMenu.run(parameters);
}
void setJournalList(RecordSortedList _journalList)
{
journalList = _journalList;
}
static void main (Args _args)
{
SalesConfirmReportEmail thisClass = new SalesConfirmReportEmail();
;
breakpoint;
if(!_args.caller())
{
throw error("Invalid calling location");
}
thisClass.parmglPrintType(_args.parmEnum());
thisClass.parmJournalList(_args.record());
thisClass.run();
}
static void postingPrint(SalesFormLetter salesFormLetter,
RecordSortedList journalList)
{
SalesConfirmReportEmail thisClass = new SalesConfirmReportEmail();
;
thisClass.parmglPrintType(salesFormLetter.SalesParmUpdate().DocDestination);
thisClass.setJournalList(journalList);
thisclass.run();
}
static glPrintType reportPrintType()
{
glPrintType printType;
;
return printType;
}
public DirPartyId GetContactEmailFromDirParty(SalesTable _salesTable)
{
;
dirPartyId = CustTable::find(_salesTable.CustAccount, false).PartyId;
return dirPartyId;
}
Now, the Run() Method is very long. Here there are a few things that I want to explain before I go on. In this method I'm saving my Axapta report in a Network folder.
Yo handle this I did not want to have a constant string where the netwrok address is assigned. The reason is that I also needed to specify an alternative address in case the Network address was not available. So, if the \\NetworkLocation.com\MyFolder is not available, then we will save the file to a temp folder in the user's machine.
For this I created a table names DocuParameters as Shown below:
Then, I'm getting the PrimaryFileOutputPath from this table, if this is not Available, then I'm getting the AlternativeFileOutputPath. I guess there are a 100 ways to do this. I just choose to do it like this as it is more centralize for my purposes.
Also, after the RUN method code, I will add a method that gets the email of an Global Address Book record or PartyID.
NOTE: In the Run() method below I'm using settings that you might not need.
void run()
{
SalesId Id;
int i;
str prtFileExt = '.pdf';
SalesFormLetter salesFormLetter = SalesFormLetter::construct(DocumentStatus::Confirmation, false);
PrintJobSettings printJobSettings = new PrintJobSettings();
Args args = new Args();
DirPartyId _partyId
;
if(printType == glPrintType::Email || printType == glPrintType::Fax || printType == glPrintType::Printer)
{
//This is where I store the Network Address
prtFilepath = DocUParameters::find().PrimaryFileOutputPath;
if ( !WinApi::fileExists(prtFilePath) )
{
prtFilePath = DocUParameters::find().AlternateFileOutputPath;
}
//Setting the print parameters to file and PDF
printJobSettings.setTarget(PrintMedium::File);
printJobSettings.format(PrintFormat::PDF);
}
else
{
if(printType == glPrintType::Preview)
{
printJobSetting = new PrintJobSettings();
// printJobSetting.unpackPrinterSettings(salesFormLetter::getPrinterSettingsFormletter(DocumentStatus::Invoice));
printJobSetting.setTarget(PrintMedium::Screen);
}
}
if(journalList)
{
if(journalList.first(CustConfirmJour))
{
Do
{
if(! printType == glPrintType::Preview)
{
prtFile = custConfirmJour.SalesId;
printJobSettings.fileName(prtFilePath + prtFile + prtFileExt);
salesFormLetter.updatePrinterSettingsFormLetter(printJobSettings.packPrintJobSettings());
select firstOnly custConfirmJour
where custConfirmJour.salesid == prtFile;
args.record(custConfirmJour);
args.caller(salesFormLetter);
new MenuFunction(menuitemoutputstr(SalesConfirmation), MenuItemType::Output).run(args);
//Here we set the file name to the file name and the extension (.pdf)
prtFile = prtFile + prtFileExt;
}
else if(printType == glPrintType::Preview)
{
printJobSetting = new PrintJobSettings();
this.printJournal(CustConfirmJour);
return;
}
salesTable = CustConfirmJour.salesTable();
if(this.parmglPrintType() == glPrintType::Email)
{
_partyId = this.GetContactEmailFromDirParty(salesTable);
info(_partyId);
//Here I call a custom method to resolve the email from a Party ID
email = salesQuotationTable.contactEmail(_partyId, EmailTypes::OrderAcknowledgement);
info(email);
if(!email)
email = ContactPerson::find(salesTable.QuoteContactPersonId, false).Email;
if(!email)
{
email = CustTable::find(custConfirmJour.OrderAccount, false).Email;
//Here I set a custom error method when contacts don't have emails
//Look for this code below after this method
salesTable.SetContactEmailErrorMessage(prtFile, prtFilePath, _partyId);
return;
}
mail = new SysINetMail_GLBTS();
mail.sendMailAttach_OLD(email, CCEmail, 'Ref.: '+ CustConfirmJour.CustomerRef +'// '+ 'Acknowledgement ' + CustConfirmJour.SalesId + '','', true, prtfilePath + prtfile, prtfile);
WinApi::deleteFile(prtfilePath + prtfile);
}
if ( this.parmglPrintType() == glPrintType::Fax )
{
fax =this.parseFaxNumber( ContactPerson::find(salesTable.quoteContactPersonId,false).TeleFax);
if(!fax)
fax =this.parseFaxNumber(CustTable::find(custConfirmJour.OrderAccount,false).Telefax);
email = fax + '@j2send.com';
}
}while(journalList.next(custConfirmJour));
}
}
}
This method is in my SalesQuotationTable
Email contactEmail(DirPartyId partyId, EmailTypes type, QuotationId quoteId = '')
{
Email email;
;
//Get Email based on DirPartyId
email = AGO_Utilities::GetContactEmailFromPartyID(partyId, type, quoteId);
return email;
}
AGO_Utilities::GetContactEmailFromPartyID is a class method - see it below
public static Email GetContactEmailFromPartyID(DirPartyId partyId, EmailTypes type, QuotationId quoteId = '')
{
DirPartyRelationship dirPartyRelationship;
Boolean breakProcess = false;
DirPartyId childPartyId;
ContactPerson contactPerson;
ContactPersonId contactPersonId;
Email email = '';
;
while select dirPartyRelationship where dirPartyRelationship.ParentPartyId == partyId
{
childPartyId = dirPartyRelationship.ChildPartyId;
if(childPartyId)
{
switch(type)
{
case EmailTypes::SalesQuotes:
//Check if sales quote has a contact to it
contactPersonId = SalesQuotationTable::find(quoteId).ContactPersonId;
if(contactPersonId)
select * from contactPerson where contactPerson.ContactPersonId == contactPersonId;
else
breakProcess = true;
break;
case EmailTypes::Invoice:
select * from contactPerson where contactPerson.PartyId == childPartyId &&
contactPerson.EmailType_Invoice == NoYes::Yes;
break;
case EmailTypes::OrderAcknowledgement:
select * from contactPerson where contactPerson.PartyId == childPartyId &&
contactPerson.EmailType_OrderAcknowledgement == NoYes::Yes;
break;
case EmailTypes::ShippingMarks:
select * from contactPerson where contactPerson.PartyId == childPartyId &&
contactPerson.EmailType_ShippingMarks == NoYes::Yes;
break;
case EmailTypes::ARStatement:
select * from contactPerson where contactPerson.PartyId == childPartyId &&
contactPerson.EmailType_ARStatement == NoYes::Yes;
break;
}
if(contactPerson.Email || contactPerson.Email2)
{
if(!contactPerson.Email)
email = contactPerson.Email2;
else
email = contactPerson.Email;
break;
}
if(breakProcess)
break;
}
}
return email;
}
The next method seats in the Sales Table sets an error message when the contact does not have an email. It creates a Info log where a user can double click on the message and go directly to a record in the Contacts Table
void SetContactEmailErrorMessage(FileName fileName, FilePath filePath, DirPartyId partyId)
{
ContactPersonId contactPersonId;
SalesQuotationTable smmQuotationTable;
SysInfoAction_FormRun SysInfoAction;
;
contactPersonId = smmQuotationTable.GetContactIdFromPartyID(partyId);
WinApi::deleteFile(filePath+fileName);
if(contactPersonId || contactPersonId != '')
{
SysInfoAction = SysInfoAction_FormRun::newFormnameDesc(FormStr(ContactPersonLookup), "@AIC586");
info('@AIC583',"", SysInfoAction_TableField::newBuffer(ContactPerson::find(contactPersonId)));
}
else
Box::info('@AIC584');
}
How do I save multiple Sales invoices to the same pdf file? I process hundreds on invoices on weekly basis and need a way to save them in one pdf file instead of saving them one by one. is there a way to do so? thank you
ReplyDeleteHi,
ReplyDeleteYou can't just save a bunch of sales orders on the same PDF without a customization.
First, it is important to remember that you can have several sales order (for the same invoice account) given one single invoice number. So, let's say you have customer account 123456789 (same invoice account for this example). You create 6 sales orders for the customer 123456789. When posting (SalesEditLine Form), you can go to the "Other Tab" and choose "Invoice Account" under the "Summary Update for" drop down list. When this is done, then you can have many sales orders in one PDF.
Now, if you want to have different sales orders on the same PDF, you will need to customize your system as this is not possible given the way AX groups the sales order data.
I hope this helps
Thank you so much for taking the time to look into this.
ReplyDeleteNot a problem. I hope you can solve your problem.
ReplyDeleteSo, just to clarify and wrap-up. You can have many sales orders in one PDF for the same customer (or invoice account).
You cannot, however, do this for different sales orders from different customers (different invoice account). In this case, AX will create one invoice for each invoice account.
Hi,
ReplyDeletePlease help me out in finding the solution to my problem. This is very
urgent.
I am creating a AX 2009 SSRS report. In the report I have two
parameters, based on the first parameter which is a drop down , the
second
parameter needs to be filtered with specific values. For eg AccountNum
is the
first parameter which displays all the CustomerId. Based on the
AccountNum selected, the second parameter should display the
AppointmentNumber. Can you
let me know how to achieve this scenario. Is this scenario possible in
AX
2009 SSRS?
Thanks & regards,
Ankita
Ankita,
ReplyDeleteYou can do this taking a few different approaches. The one I recommend if you are new to SSRS and AX is the following:
1- Create a DataSet in Visual Studio that is linked to a query in AX that brings all the customer id's. This query in AX already exists, so you don't have to build anything.
2- For the second parameter, you will have to write a data method in Visual Studio that calls a method (and/or custom class) in AX. So, for this you will have to create a tmpTable in AX that will be used to store the results of what the info related to a customer id.
Then, I would recommend creating a class and 1 static method. The static will have the name of the tmpTable, so you will return this table back to visual studio.
So you will write public static tmpTable_AppoinemntNumbers(CustNum _custNum)
The method will receive the customer id as a parameter and with this you can create a while loop that will insert the values into your tmpTable, then in Visual Studio, your data method will have to return a data table, so it will be like this
public DataTable getAppointments (string custNum)
Then, you will have to call your new class and assign it to the data table from C# and then set it back to your appointment parameter value in your SSRS report.
Let me know if you have any questions.
Hi,
ReplyDeleteThanks for your reply.
I have never create any data method so please help me in making it and please tell me the process step wise so that i will be able to solve my issue.
Thanks in advance.
Hi,
DeletePlease look at the following links, they will help you understand ow to create a report for AX in visual studio, and create a report data method.
Introduction to Reporting with Visual Studio 2008
http://msdn.microsoft.com/en-us/library/cc622374(v=ax.50).aspx
Report Data Method Overview
http://msdn.microsoft.com/en-us/library/cc587341(v=ax.50).aspx
I think it would be a good idea for you to take a few hours and read through this material. Also, if yopu can, try to follow a walkthrough exercise and you'll get it right away.
I hope this helps!
Hi,
DeleteI am trying to learn that how to create data methods and already seeing the same links which you have told but if you will help me out in writing data methods then it will be benefited for me because till now i haven't create it.
I also want to know that where I will create a class and 1 static method (in ax or in Visual Studio). And i didn't get this statement - "The method will receive the customer id as a parameter and with this you can create a while loop that will insert the values into your tmpTable, then in Visual Studio, your data method will have to return a data table, so it will be like this
public DataTable getAppointments (string custNum)
Then, you will have to call your new class and assign it to the data table from C# and then set it back to your appointment parameter value in your SSRS report".
Please elaborate that how to perform the steps mentioned by you in the previous post.
Ankita,
DeleteI'm sorry you didn't get what you were expecting for the links. It seems that you are new to this and it can be frustrated when we cannot find the exact answer we are looking for.
Please follow and execute each of the following exercises. I'm sure you will be able to get what you need from them as they cover everything you need to accomplish.
http://www.youtube.com/watch?v=JVNisza0BQI
http://www.youtube.com/watch?v=JVNisza0BQI
http://msdn.microsoft.com/en-us/library/cc622728(v=ax.50).aspx
http://msdn.microsoft.com/en-us/library/cc636713(v=ax.50).aspx
http://msdn.microsoft.com/en-us/library/cc639019(v=ax.50).aspx
I hope this helps!
Hi,
ReplyDeleteI am using AX2012 and I am trying to send invoice directly to customers via email. When the invoice is attached to the email it should bear the name as Customer name - Invoice no. How to automate this when we post the invoice. What changes should i make to your code given above to achieve it. I will be saving files locally and not on any network.
Thanks you have already done a very job keep up the good work.
Hi!
DeleteIf I'm not mistaken, the email invoice function comes with AX 2012.
Let's take the following from MSDN:
Send e-mail
"Select this check box to send the invoice for a sales order to the customer as an email attachment after the invoice is posted. Attachments are sent as PDF and XML files. This check box is available only if you select the Enable CFD (electronic invoices) check box in the Electronic invoice parameters form."
The above instructions comes from here http://msdn.microsoft.com/en-us/subscriptions/aa550287.aspx
Just click the link and search for the word "Email"
Now, if you still want to write your own code (Which I would not recommend with AX 2012), please take a look at the following code and see what serves your purpose.
Thanks for reading my blog and let me know if you have any questions.
Args args=new Args();
SalesFormLetter salesFormLetter;
SysMailer mailer;
PrintJobSettings printJobSettings;
str _fileName;
InteropPermission permission;
FileIOPermission dirPermission, filePermission;
Set permissionSet;
;
args.record(_custInvoiceJour);
_fileName=strfmt('%1\\%2.pdf',filepath,_custInvoiceJour.InvoiceId);
if(_custInvoiceJour)
{
salesFormLetter = SalesFormLetter::construct(DocumentStatus::Invoice,false);
printJobSettings = new printJobSettings();
printJobSettings.setTarget(PrintMedium::File);
printJobSettings.format(PrintFormat::PDF_EMBED_FONTS);
printJobSettings.fileName(_filename);
salesFormletter.updatePrinterSettingsFormLetter(printJobSettings.packPrintJobSettings());
_custInvoiceJour.printJournal(SalesFormletter);
{
permission = new InteropPermission(InteropKind::ComInterop);
dirPermission = new FileIOPermission(_fileName,'W');
permissionSet = new Set(Types::Class);
permissionSet.add(permission);
permissionSet.add(dirPermission);
CodeAccessPermission::assertMultiple(permissionSet);
mailer = New SysMailer();
mailer.fromAddress('abc@xyz.com');
mailer.subject(strfmt("Invoice %1,date %2", _custInvoiceJour.invoiceId,_custInvoiceJour.InvoiceDate));
if(email) //<--dialog field
{
mailer.tos().appendAddress(email);
}
if(!email)
{
mailer.tos().appendAddress(CustTable::find(_custInvoiceJour.InvoiceAccount).Email);
}
mailer.htmlBody("Please see attached");
mailer.SMTPRelayServer( SysEmailParameters::find().SMTPRelayServerName,
SysEmailParameters::find().SMTPPortNumber,
SysEmailParameters::find().SMTPUserName,
SysEmailParameters::find().SMTPPassword,
SysEmailParameters::find().NTLM);
mailer.attachments().add(_fileName);
mailer.sendMail();
}
CodeAccessPermission::revertAssert();
}
Good Luck!
thank for post the links..
ReplyDeleteand
i need AIF&EP Fundamentals ple send blog names or creation steps post here...ple
Nice one . U r doing well job.
ReplyDeleteThis is axapta ERP blog for Technical and functional fields and includes Microsoft Dynamics Axapta tutorials and Dynamics Axapta Coverage. This blog also contains x++ code help for Ax developer and solution of technical and functional daily issues. This blog is specific for Microsoft dynamics programming. Enterprise portal, SharePoint services, business connectors and Enterprise Resource Planning applications and sql database.It will help to get Microsoft Business Solutions.
axapta ERP blog
hi
ReplyDeleteCan u please help me out this issue ax 2009 ,
i want to send one Report pdf file on email thorugh batch process ...For this run time i am generating the report reportrun.run and saveing in one folder , my issue was when i am runing the batch (normal process) report is generating as 38 Kb it is working file , when i attached to batch process it is giveing me report 1 kb it is not generating may i no the reason.