Wednesday, October 13, 2021

Populate data from Sales quotation line to Sales order line X++

Hi guys,

This blog will be helpful to handle such cases where in data needs to be populated from one source document to the other source document. In D365 Fin Ops, each end to end process is linked with different source documents and all these source documents have reference with each other. 

For example: Order to cash end to end process involves multiple source documents starting from Sales quotation all the way to Customer payments. 

Here is the Order to cash process follow:

Sales Quotation -> Sales order -> Picking list -> Packing slip -> Sales invoice -> Customer payments

Requirement:

Recently, I had a requirement where in customer requested to add Notes field on Sales quotation line as well as on Sales order line and wanted to populate data automatically from Sales quotation line to Sales order line. To further explain the scenario, User enters data on Notes field in Sales quotation line and this Notes data has to be populated to Notes field on Sales order line to avoid entering Notes data again in Sales order line. The purpose of this customization is to print notes on the Sales invoice lines just for the reference purpose.

Solution:

Create an extension class of Sales line table

 [ExtensionOf(tableStr(SalesLine))]   
 final class SalesLine_Extension   
 {   
      //This method will take care of populating Notes data from Sales quotation line to Sales order line   
      //Always check if there is any initfrom method available at table level as this method will default data //from one table to the other table   
      public void initFromSalesQuotationLine(SalesQuotationLine _salesQuotationLine)   
      {   
           next initFromSalesQuotationLine(_salesQuotationLine);   
           //Below line will be executed after standard MS call due to next chain of command   
           //Notes data will be defaulted from source Sales quotation line to destination Sales line table   
           this.Notes   = _salesQuotationLine.Notes;   
      }   
 }   

Tuesday, October 12, 2021

Purchase order print outs to respective person automatically by email X++

Hi folks,

This blog will be useful for customizing requirements that are related to sending document prints (Purchase order, Sales Invoice etc) internal or external via email.

Requirement:

I got the requirement where in Procurement team wanted to send PO print outs to the buyers automatically through emails as soon as POs are confirmed. Since, PO was created from RFQ as a part of process and RFQ had a reference of buyers so buyer worker ID reference from RFQ was being used in order to get buyer email address on which PO print out email had to be sent.

Solution: 

To automate PO print out via email, there must be trigger point on which email has to be sent. PO print out is generated at the time of confirming the POs therefore this trigger point of PO confirmation is being used to sent out emails to the buyers. Since, same PO can have multiple confirmation versions so to avoid sending same PO confirmation version to the buyers, there is a flag added on the PO confirmation version table that identifies whether PO confirmation version had been sent to the buyer or not. If PO is not yet sent to the buyer then flag will be No and system will send that PO to the buyer otherwise PO will not be sent to the buyer in case of flag set as Yes.

Create a class which is extended from batch class that is used to process the events or actions periodically. In this case email sending to the buyers is the periodic event that occurs every time when PO is confirmed.

//This class is used to process the email action periodically using batch framework class

class POConfirmationAttachmentsToBuyer extends RunBaseBatch

{

    public void run()
    {
        VendPurchOrderJour          vendPurchOrderJour;
        PurchTable                         purchTable;
           
        super();

        while select firstonly vendPurchOrderJour
        order by RecId desc
        join  purchTable
        where purchTable.PurchId == vendPurchOrderJour.PurchId
        &&    vendPurchOrderJour.IsEmailSent == NoYes::No 
        {
                this.runAndSaveSSRSReport(vendPurchOrderJour);
        }
    }

    public void runAndSaveSSRSReport(vendPurchOrderJour _vendPurchOrderJour)
    {
        PurchReqLine                                                  purchreqline;
        PURCHRFQCASETABLE                             purchRFQCaseTable;
        PurchLine                                                        purchLine; 
        container                                                          binData;
        Binary                                                              binaryData;
          
        try
       {
            select  firstonly PurchReqLineRefId from  purchLine
            where purchLine.PurchId                          == _vendPurchOrderJour.PurchId;
            join     purchreqline
            where purchreqline.LineRefId                   == purchLine.PurchReqLineRefId;
            join     purchRFQCaseTable
            where PurchRFQCaseTable.RFQCaseId   == purchreqline.PURCHRFQCASEID;

            SrsReportRunController          controller = new SrsReportRunController();
            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();
           Args                                    args;
           args = new Args();
           controller.parmArgs(args);
           controller.parmReportName(ssrsReportStr(PurchPurchaseOrderExtCopy, Report));
           controller.parmShowDialog(false);
           controller.parmLoadFromSysLastValue(false);
           contract = controller.parmReportContract().parmRdpContract();
           contract.parmRecordId(_vendPurchOrderJour.RecId);
           settings = controller.parmReportContract().parmPrintSettings();
           settings.printMediumType(SRSPrintMediumType::File);
           settings.fileName(_vendPurchOrderJour.PurchId + "-" +                                                                    VendTable::find(_vendPurchOrderJour.OrderAccount).name());
           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());            
            System.IO.MemoryStream mstream = new System.IO.MemoryStream(reportBytes);
            binaryData = Binary::constructFromMemoryStream(mstream);
            if(binaryData)
           {
                binData = binaryData.getContainer();
           }
            Map map=new Map (Types::String,Types::String);
            map.insert(strFmt("Dear %1,<o:p></o:p></p><p></p><p>Please find attached PO Print out.                 <o:p></o:p></p><p></p><p>Regards,<o:p></o:p></p><p></p><p><span style='font-                        size:12.0pt'> ERP Team<o:p></o:p></span>                    </p>",HcmWorker::find(PurchRFQCaseTable.Requester).name()), "message");
       
            POConfirmationEmailSentToBuyers::sendPDFEamilAttachment("PO", "en-                US",HcmWorker::find(PurchRFQCaseTable.Requester).email(), map, binData,             _vendPurchOrderJour.PurchId + "-" + VendTable::find(_vendPurchOrderJour.OrderAccount).name());

            ttsbegin;
            
            _vendPurchOrderJour.selectforupdate(true);
            
            _vendPurchOrderJour.IsEmailSent = NoYes::Yes;
            _vendPurchOrderJour.doUpdate();

            ttscommit;
        }
        catch(Exception::Error)
        {
            ttsbegin;

            _vendPurchOrderJour.selectforupdate(true);
        
            _vendPurchOrderJour.IsEmailSent = NoYes::No;
            _vendPurchOrderJour.doUpdate();

            ttscommit;
        }
    }
}

//This class is used to send emails to the Buyers
public static class POConfirmationEmailSentToBuyers
{
    public static void sendPDFEamilAttachment(SysEmailId      _emailId,
        LanguageId      _language,
        SysEmailAddress             _emailAddr,
        Map             _mappings,container _binData,str _fileName)
    {
        SysEmailItemId                   nextEmailItemId;
        SysEmailTable                    sysEmailTable;
        SysEmailMessageTable             sysEmailMessageTable;
        SysEmailContents                 sysEmailContents;
        SysOutgoingEmailTable            outgoingEmailTable;
        SysOutgoingEmailData             outgoingEmailData;
        Filename filename, FileExtension;
        Str1260  emailcontents, filNameSplit;
      
        FileExtension="";
       
        select sysEmailTable
               join    sysEmailMessageTable
                    where sysEmailMessageTable.EmailId== _emailId
                    && sysEmailMessageTable.LanguageId==_language;
       
        if(sysEmailTable.RecId>0)
        {
            emailcontents = POConfirmationEmailSentToBuyers::map2str(_mappings);
            sysEmailContents= emailcontents;
            
            nextEmailItemId = EventInbox::nextEventId();
      
            filename =strFmt("%1_%2.pdf",nextEmailItemId,_fileName);
    
            outgoingEmailTable.clear();
            outgoingEmailTable.Origin =sysEmailTable.Description;
            outgoingEmailTable.EmailItemId = nextEmailItemId;
            outgoingEmailTable.IsSystemEmail = NoYes::Yes;
            outgoingEmailTable.Sender = sysEmailTable.SenderAddr;
            outgoingEmailTable.SenderName = sysEmailTable.SenderName;
            outgoingEmailTable.Recipient = _emailAddr;
            outgoingEmailTable.Subject = "System generated PO print out notification";
            outgoingEmailTable.Priority = eMailPriority::High;
            outgoingEmailTable.WithRetries = NoYes::NO;
            outgoingEmailTable.RetryNum = 0;
            outgoingEmailTable.UserId = curUserId();
            outgoingEmailTable.Status = SysEmailStatus::Unsent;
            outgoingEmailTable.Message =  sysEmailContents;
            outgoingEmailTable.LatestStatusChangeDateTime = DateTimeUtil::getSystemDateTime();
            outgoingEmailTable.TemplateId = _emailId;
            outgoingEmailTable.insert();


            if(conLen(_binData)>0)
            {
                outgoingEmailData.clear();

                outgoingEmailData.EmailItemId = nextEmailItemId;
                outgoingEmailData.DataId = 1;
                outgoingEmailData.EmailDataType = SysEmailDataType::Attachment;
                outgoingEmailData.Data = _binData;
                filNameSplit = subStr(filename, 12, 100);

                outgoingEmailData.FileName = filNameSplit;
                outgoingEmailData.FileExtension =FileExtension;

                outgoingEmailData.insert();
            }
            info(strfmt("PO sent successfully to %1", _emailAddr));
        }
    }

    public static Str1260 map2str(Map _map, boolean _returnKey = true, str _separator = ', ')
    {
         str ret;
         MapEnumerator e = _map.getEnumerator();

        while (e.moveNext())
        {
             if (ret) ret += _separator;
             ret += any2str(_returnKey ? e.currentKey() : e.currentValue());
        }
        return ret;
    }
}

Enable and Disable Vendor invoice button on PO list and details page X++

Hi folks, This blog will be useful where vendor invoice process is required to be controlled based on certain conditions. Requirements: Proc...