Wednesday, July 24, 2024

D365 Sending Email with Customer Account Statement SSRS report as attachment using X++

D365 Sending Email with Customer Account Statement SSRS report as attachment using X++




 custTable _custTable;
              

                SysOperationQueryDataContractInfo sysOperationQueryDataContractInfo;
                SrsReportRunController reportRunController;
                CustTransListContract custTransListContract;
                SRSReportExecutionInfo reportExecutionInfo;
                SRSPrintDestinationSettings printDestinationSettings;
                SRSReportRunService srsReportRunService;
                SRSProxy srsProxy;
                QueryBuildRange qbrCustAccount;
                QueryBuildDataSource queryBuildDataSource;
                Object dataContractInfoObject;
                Map reportParametersMap;
                Map mapCustAccount;
                MapEnumerator mapEnumerator;
                Array arrayFiles;
                System.Byte[] reportBytes;
                Filename fileName;
                Args args;
                System.IO.MemoryStream memoryStream;
                System.IO.MemoryStream fileStream;
                CustParameters custParameters;
                Email toEmail;


                Map                                 templateTokens;
                str                                 emailSenderName;
                str                                 emailSenderAddr;
                str                                 emailSubject;
                str                                 emailBody;
 
                Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
 
                #define.Subject("Subject")
                #define.CustAccount("CustAccount")
                #define.EmailDate("Date");
 
                custParameters          = CustParameters::find();
 
                reportRunController     = new SrsReportRunController();
                custTransListContract   = new CustTransListContract();
                reportExecutionInfo     = new SRSReportExecutionInfo();
                srsReportRunService     = new SrsReportRunService();
                reportBytes             = new System.Byte[0]();
                args                    = new Args();
                templateTokens          = new Map(Types::String, Types::String);
                var messageBuilder      = new SysMailerMessageBuilder();
 
                // custTransListContract.parmNewPage(NoYes::Yes);
                str custAccount = "10000";
                _custTable = CustTable::find(custAccount);
                if(!_custTable)
                {
                    return "No Customer found";
                }
                if(!emailAddress)
                {
                    return "No emailAddress";
                }
                fileName    = strFmt("CustomerAccountStatement_%1.pdf", _custTable.AccountNum);
 
                reportRunController.parmArgs(args);
                reportRunController.parmReportName(ssrsReportStr(CustTransList, Report));
                reportRunController.parmShowDialog(false);
                reportRunController.parmLoadFromSysLastValue(false);
                reportRunController.parmReportContract().parmRdpContract(custTransListContract);
 
                // Modify query
                mapCustAccount = reportRunController.getDataContractInfoObjects();
                mapEnumerator = mapCustAccount.getEnumerator();
 
                while (mapEnumerator.moveNext())
                {
                    dataContractInfoObject = mapEnumerator.currentValue();
 
                    if (dataContractInfoObject is SysOperationQueryDataContractInfo)
                    {
                        sysOperationQueryDataContractInfo = dataContractInfoObject;
 
                        queryBuildDataSource    = SysQuery::findOrCreateDataSource(sysOperationQueryDataContractInfo.parmQuery()
                                                                    , tableNum(CustTable));
                        qbrCustAccount          = SysQuery::findOrCreateRange(queryBuildDataSource, fieldNum(CustTable, AccountNum));
                        qbrCustAccount.value(_custTable.AccountNum);
                    }
                }
 
                printDestinationSettings = reportRunController.parmReportContract().parmPrintSettings();
                printDestinationSettings.printMediumType(SRSPrintMediumType::File);
                printDestinationSettings.fileName(fileName);
                printDestinationSettings.fileFormat(SRSReportFileFormat::PDF);
 
                reportRunController.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());
                reportRunController.parmReportContract().parmReportExecutionInfo(reportExecutionInfo);
 
                srsReportRunService.getReportDataContract(reportRunController.parmreportcontract().parmReportName());
                srsReportRunService.preRunReport(reportRunController.parmreportcontract());
 
                reportParametersMap = srsReportRunService.createParamMapFromContract(reportRunController.parmReportContract());
                parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
 
                srsProxy        = SRSProxy::constructWithConfiguration(reportRunController.parmReportContract().parmReportServerConfig());
                reportBytes     = srsproxy.renderReportToByteArray(reportRunController.parmreportcontract().parmreportpath()
                                                    , parameterValueArray
                                                    , printDestinationSettings.fileFormat()
                                                    , printDestinationSettings.deviceinfo());
 
                memoryStream    = new System.IO.MemoryStream(reportBytes);
                memoryStream.Position = 0;
 
                fileStream      = memoryStream;
                toEmail         =  this.getCustEmail(_custTable.AccountNum);
 
      

                    emailBody = "Dear Customr, Please find attached Customer account statement till today.";
                    emailSubject = strFmt("Customer account statement for %1", _custTable.AccountNum);
                    emailSenderAddr = "msd@test.com";
                    emailSenderName = strFmt("Customer account statement for %1", _custTable.AccountNum);


                    messageBuilder.addTo(toEmail)
                        .setSubject(emailSubject)
                        .setBody(SysEmailMessage::stringExpand(emailBody, SysEmailTable::htmlEncodeParameters(templateTokens))).addCC("");
 
                 
                    messageBuilder.setFrom(emailSenderAddr , emailSenderName );
           
                    messageBuilder.addAttachment(fileStream, fileName);
 
                    SysMailerFactory::sendNonInteractive(messageBuilder.getMessage());



public Email getCustEmail(CustAccount _custAccount)
{
    CustTable                   custTable;
    DirPartyLocation            dirPartyLocation;
    LogisticsLocation           logisticsLocation;
    LogisticsElectronicAddress  logisticsElectronicAddress;
 
    custTable = CustTable::find(_custAccount);
 
    select firstonly Location, Party from dirPartyLocation
        where dirPartyLocation.Party                        == custTable.Party
            join RecId from logisticsLocation
                where logisticsLocation.RecId               == dirPartyLocation.Location
            join Locator from logisticsElectronicAddress
                where logisticsElectronicAddress.Location   == logisticsLocation.RecId
                    && logisticsElectronicAddress.Type      == LogisticsElectronicAddressMethodType::Email
                    && logisticsElectronicAddress.IsPrimary == NoYes::Yes;
 
    return logisticsElectronicAddress.Locator;
}

Tuesday, July 23, 2024

clicking link on info message X++ to Open form

 Message::AddAction() method can be used to embed an action within a message sent to the message bar. This method supports adding a single action that is associated with a display or action menu item, which is then visualized as a link button, redirecting users directly to the form with the correct record selected. Here's how it can be used.



public static void pushNotification(str  NotificationMsg, str actionText,LedgerJournalTable journalTable)
{
    MenuItemMessageAction actionData = new MenuItemMessageAction();
    actionData.MenuItemName(menuItemDisplayStr(LedgerJournalTable));
    actionData.TableName(tableStr(LedgerJournalTable));
    actionData.RecId(journalTable.RecId);
    
    str jsonData = FormJsonSerializer::serializeClass(actionData);
 
    int64 messageId = Message::AddAction(MessageSeverity::Informational,NotificationMsg ,actionText, MessageActionType::DisplayMenuItem, jsonData);
} 

Muhammad Aamir Hanif Reference 


Thursday, November 30, 2023

D365 FO - Send Email with attachments by X++

 

Go to System administration >> Setup >> Email >> System email templates

Create New template 


[ExtensionOf(classStr(PurchConfirmationJournalPost))]
final class PurchConfirmationJournalPost_Extension
{
    public str  CCEmail, Esender;

    public void Sendmail()
    {
        try
        {
            str vendname = purchTable::find(this.vendPurchOrderJour.purchid).vendorName(); 
            str Purchid = this.vendPurchOrderJour.purchid;
            str sUserId;
             
                sUserId = curUserId();
                //Getting Email id from User Options --> Account --> Email Provider --> Email id
                str userEmail = SysUserInfo::find(sUserId, false).Email;
            
                var builder = new SysMailerMessageBuilder();
                builder.setBody(this.Email_Body());
                builder.setFrom(userEmail,Esender);
                builder.addcc(ccEmail);
               
                builder.addTo("test@test.com");
                builder.setSubject("subject string");
            
                //************
                System.IO.Stream     stream = this.generateReportStream(); // todo insert record reference
                if(stream)
                    builder.addAttachment(stream , vendname +" - "+Purchid+".pdf");
                var message = builder.getMessage();
               
              SysMailerFactory::getNonInteractiveMailer().sendNonInteractive(message);
            
        }
        catch (Exception::Error)
        {
            throw error("@SYS33567");
        }
    }


  public str Email_Body()
    {
        str vendname = purchTable::find(this.vendPurchOrderJour.purchid).vendorName();
        str Purchid = this.vendPurchOrderJour.purchid;

        str sub,tmp,Sender,frm,cc;
        [sub,tmp,Sender,frm,cc] =   this.getEmailTemplate('POConfirm','en-us');
        Esender  = sender;
        tmp = strReplace(tmp,'%vend%','%1');
        tmp = strReplace(tmp,'%purchid%','%2');
        str ret = strFmt(tmp,vendname,Purchid);

        return ret;
    }

 public  container getEmailTemplate(SysEmailId _emailId, LanguageId _languageId)
    {
        // Info(_emailId);

        SysEmailMessageSYSTEMTable  messageTable    = SysEmailMessageSYSTEMTable::find(_emailId, _languageId);
        SysEmailSYSTEMTable         emailTable      = SysEmailSYSTEMTable::find(_emailId);
      
        //  Info(emailTable.DefaultLanguage);

        if (!messageTable && emailTable)
        {
            // Try to find the email message using the default language from the email parameters
            messageTable = SysEmailMessageSYSTEMTable::find(_emailId, emailTable.DefaultLanguage);
            
        }

        if (messageTable)
        {
          //  info(emailTable.CCEmail);
            CCEmail = emailTable.CCEmail;
            return [messageTable.Subject, messageTable.Mail, emailTable.SenderAddr, emailTable.CCEmail];
        }
        else
        {
            warning("We didn't find a template"); // Let the user know we didn't find a template
            return ['', '', emailTable.SenderAddr, emailTable.SenderName, emailTable.ccemail];
        }
    }
 public System.IO.MemoryStream generateReportStream()
    {
        DocuRef                         addedRecord;
        VendPurchOrderJour              VendPurchOrderJour = this.vendPurchOrderJour;
        Filename                        fileName = "Report.pdf";
        PurchPurchaseOrderController controller = new PurchPurchaseOrderController();
        PurchPurchaseOrderContract    contract = new PurchPurchaseOrderContract();
        SRSPrintDestinationSettings     settings;
        Array                           arrayFiles;
        System.Byte[]                   reportBytes = new System.Byte[0]();
        SRSProxy                        srsProxy;
        SRSReportRunService             srsReportRunService = new SrsReportRunService();
        Microsoft.Dynamics.AX.Framework.Reporting.Shared.ReportingService.ParameterValue[]  parameterValueArray;
        Map                             reportParametersMap;
        SRSReportExecutionInfo          executionInfo = new SRSReportExecutionInfo();
        System.IO.MemoryStream          stream;
        Args                            args = new Args();       ;

        args.record(VendPurchOrderJour);
        contract.parmRecordId(VendPurchOrderJour.RecId);
        controller.parmArgs(args);
        controller.parmReportName(ssrsReportStr(PurchPurchaseOrder, Report));
        controller.parmShowDialog(false);
        controller.parmReportContract().parmRdpContract(contract);
        settings = controller.parmReportContract().parmPrintSettings();
        settings.printMediumType(SRSPrintMediumType::File);
        settings.fileName(fileName);
        settings.fileFormat(SRSReportFileFormat::PDF);
        controller.parmReportContract().parmReportServerConfig(SRSConfiguration::getDefaultServerConfiguration());
        controller.parmReportContract().parmReportExecutionInfo(executionInfo);
        srsReportRunService.getReportDataContract(controller.parmreportcontract().parmReportName());
        srsReportRunService.preRunReport(controller.parmreportcontract());
        reportParametersMap = srsReportRunService.createParamMapFromContract(controller.parmReportContract());
        parameterValueArray = SrsReportRunUtil::getParameterValueArray(reportParametersMap);
        srsProxy = SRSProxy::constructWithConfiguration(controller.parmReportContract().parmReportServerConfig());
        reportBytes = srsproxy.renderReportToByteArray(controller.parmreportcontract().parmreportpath(),
                                              parameterValueArray,
                                              settings.fileFormat(),
                                              settings.deviceinfo());

        if (reportBytes)
        {
            stream = new System.IO.MemoryStream(reportBytes);
        }
        return stream;
    }
  }


Monday, November 27, 2023

D365 FO Data entity Export using X++

 

Data entity Export using X++ in D365 FO

 Export data entity through X++ in D365 FO.




public final class ExportEntity
{
    public static void main(Args _args)
    {
        #dmf
        Query 				query;
        DMFEntityName 			entityName = "Batch groups";
        SharedServiceUnitFileID 	fileId;       

        // Update query
	// query = new Query(DMFUtil::getDefaultQueryForEntity(entityName));
	query = new query(dmfutil::getDefaultQueryForEntityV3(entityname));
		
        querybuilddatasource qbds = query.datasourcetable(tablenum(BatchGroupEntity));
        sysquery::findorcreaterange(qbds, fieldnum(BatchGroupEntity, ServerId)).value("Batch:DEMO");

        // Export file
        DMFDefinitionGroupName definitionGroupName = 'BatchGroupEntityExport';
		
        try
        {
            DMFEntityExporter exporter = new DMFEntityExporter();

            //There are optional parameters also added
            fileId = exporter.exportToFile(
            entityName,            //Entity label
            definitionGroupName,    //Definition group
            '',                    //ExecutionId group
            'CSV',                 // or 'XML-Element',//Source format to export in
            #FieldGroupName_AllFields,//Specify the field group fields to include in export.
            query.pack(),            //Query criteria to export records
            curExt(),//
            null,                //List of XSLT files
            true,                //showErrorMessages
            false);                //showSuccessMessages

            if (fileId != '')
            {
                //Get Azure blob url from guid
                str downloadUrl = DMFDataPopulation::getAzureBlobReadUrl(str2Guid(fileId));

                System.Uri uri = new System.Uri(downloadUrl);
				
                str fileExt;

                if (uri != null)
                {
                    fileExt = System.IO.Path::GetExtension(uri.LocalPath);
                }

                Filename filename = strFmt('CustomerPaymentData%1',fileExt);
                System.IO.Stream stream = File::UseFileFromURL(downloadUrl);
				
                //Send the file to user
                File::SendFileToUser(stream, filename);
                
                // Below code will delete the export group.
                //DMFDefinitionGroup::find(definitionGroupName, true).delete();
            }
            else
            {
                throw error("The file was not generated succefully. See execution log");
            }
        }
        catch
        {
            throw error("DMF execution failed");
        }

    }
}

References   Ref  Ref2

D365 FO Encryption And Decryption Using A Symmetric Key(AES) using X++

 

Encryption And Decryption Using A Symmetric Key(AES) using x++

 Encryption And Decryption Using A Symmetric Key(AES) using x++.


I received a requirement to generate an XML file with encrypted data using a symmetric key. The recipients on the other side will decrypt the text using the same symmetric key. To test this, I used a dummy value such as 'RRR'.

To achieve this, I wrote the code in C# and added the resulting DLL to my project references.


C# Code:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace EncryptionDecryptionUsingSymmetricKey
{
    public class AesOperation
    {
        public static string EncryptString(string key, string plainText)
        {
            byte[] iv = new byte[16];
            byte[] array;

            using (Aes aes = Aes.Create())
            {
                aes.Key = Encoding.UTF8.GetBytes(key);
                aes.IV = iv;

                ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        using (StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
                        {
                            streamWriter.Write(plainText);
                        }

                        array = memoryStream.ToArray();
                    }
                }
            }

            return Convert.ToBase64String(array);// It will convert bytes to string
        }

        public static string DecryptString(string key, string cipherText)
        {
            byte[] iv = new byte[16];
            byte[] buffer = Convert.FromBase64String(cipherText); // It will convert string to bytes
using (Aes aes = Aes.Create()) { aes.Key = Encoding.UTF8.GetBytes(key); aes.IV = iv; ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); using (MemoryStream memoryStream = new MemoryStream(buffer)) { using (CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read)) { using (StreamReader streamReader = new StreamReader((Stream)cryptoStream)) { return streamReader.ReadToEnd(); } } } } } } }



X++ Code:

b14ca5898a4e4133bbce2ea2315a1916

Using EncryptionDecryptionUsingSymmetricKey;

internal final class TestRunnableClass1
{
    public static void main(Args _args)
    {
        str key = "b65ff7654brt8799fghj4ed7892b6798";
 
        str	text = 'RRR';
 
        str encryptedString = AesOperation::EncryptString(key, text);
 
        info(encryptedString);
 
        str decryptedString = AesOperation::DecryptString(key, encryptedString);
 
        info(decryptedString);
    }
}
References   Ref1  Ref2 


Get multiple selected rows using D365 X++

 

First You need to insure that Multi Select for Button is True 





Create your extension class and add follow code below 


  [FormControlEventHandler(formControlStr(CustFreeInvoice, MultipleSelect), FormControlEventType::Clicked)]
    public static void  MultipleSelect_OnClicked(FormControl sender, FormControlEventArgs e)
    {
        MultiSelectionHelper          selectionHelper = MultiSelectionHelper::construct();
        Set                           selectedRecords = new Set(Types::Record);
        FormRun formrun                         = sender.formRun();
        CustInvoiceTable _CustInvoiceTable   = formrun.dataSource().cursor();
        CustInvoiceTable                       myTable;
     //   super();
    
        FormDataSource     _CustInvoiceTable_ds = sender.formRun().dataSource(formDataSourceStr(CustFreeInvoice,CustInvoiceTable)) as FormDataSource;
      
        selectionHelper.parmDataSource(_CustInvoiceTable_ds);
        myTable  = selectionHelper.getFirst();
 
        if (myTable.RecId)
        {
            while (myTable)
            {
                selectedRecords.add(myTable);
                info(strFmt("Selected record is %1",myTable.RecId));//Display selected record
                myTable = selectionHelper.getNext();
            }
        }
    }





Monday, November 20, 2023

D365 Solution for Cloud Dev machine show error ' The remote certificate is invalid according to the validation procedure. '

 

You are getting 'The remote certificate is invalid according to the validation procedure.'Error show with all reports and  tired Rotate SSL Certificate but not complete.




will require applying the below steps and if still the same issue after this so you will need to redeploy this environment.
  1. Navigate to Lifecycle Services.

  2. In the Shared Asset library, click the Model

  3. Download the Renew WinRM certificate folder

  4. RDP to the environment

  5. Extract the zip file to a local folder

  6. Open mmc.exe and add Certificate snap-in (Computer)

  7. Open Personal\Certificates and locate the certificate with your VMs name which should have the expiration date passed

  8. Browse to the RenewWinRMCertificate folder that was previously created from extracting the zip file

  9. Select File > Open Windows PowerShell with elevated privileges (Run as Administrator)

  10. Run .\VirtualMachine-RegenerateWinRMCertificate.ps1 from the folder

  11. Refresh the Certificates console and confirm the Certificate has been created and the expiration date is now valid. (Optional: Delete expired certificate to avoid confusion)

  12. Restart the Environment from LCS (Stop \ Start)

  13. In LCS select Maintain > Rotate secrets

  14. Select 'Rotate the SSL certificates'

How to update the WinRM SSL certificate on environments deployed in your subscription - Microsoft Dynamics 365 Blog

Rotate the expired or nearly expired SSL certificate on your subscription's one-box environments - Microsoft Dynamics 365 Blog



in case when running step number 10  (Run .\VirtualMachine-RegenerateWinRMCertificate.ps1 from the folder ) and show below error 


1: Configure Network Security Rules>Go to Network Security Group of the Azure VM and change 'powershell-remote-rule' from Deny to Allow (this is just temporary; we will change it back after completing the rotation). 

Then run script It should work fine.