PDF Merger – a starting point

One of our customer service’s (CS) requirement was to be able to retrieve from M3 a combined .pdf file containing the 3 main documents they usually send to the forwarding agent :

  • CMR (MWS610PF) : used for customs, international shipments
  • Customer invoice (OIS199PF)
  • Packing list (MMS480PF)

This requirement is not possible to meet in standard, even with Infor Document Management (IDM) installed within our M3 environment.

At the time being, CS team must :

  • retrieve one by one each correct .pdf file (we have duplicates on CMR… to solve)
  • print each document
  • scan the batch of files and send it to their personal mailbox
  • forward it to the proper “mail to”

The idea here is to offer the possibility to the final user to easily trigger “on demand” the combined .pdf files for one delivery index and receive it automatically in their mailbox.

 

This post is a starting point and present what can be done. The process can certainly be enhanced, so feel free to comment.

 

Technical choice made :

  • create a widget in Smart Office : allow document selection and delivery number entry
  • check data entered, return error if needed
  • trigger a MEC mapping that will fetch the requested files through a XQuery in IDM database (that’s where the IDM API play a role) + merge the files + send the result by email with attachment

note : MEC has been used because i actually don’t know well how to use an assembly in .NET but i am sure we can build a java program and call it directly from the script.

 

IDM – Infor Document Managment

In IDM, you can XQuery the database, asking for different documents tied to a given delivery number for example, but you can only select one line at a time. And there is no merge possibility at the time being.


Infor provides useful java APIs for IDM, already discussed in Thibaud’s post https://m3ideas.org/tag/document-archive/

 

The useful one is icp.jar that you can decompile :

 

The widget

Inspired by another post from Thibaud (https://m3ideas.org/2012/05/08/stand-alone-scripts-for-smart-office/), we create a shortcut on the canvas that will launch the script behind.


 

The code is pretty straightforward :

var task = new Task(new Uri('jscript://'));
task.AllowAsShortcut = false;
task.VisibleName = 'PDF Merger';
var runner =DashboardTaskService.Current.LaunchTask(task,HostType.Widget);
runner.Status = RunnerStatus.Running;
var host = runner.Host;
host.HostContent = CreateWindow();

host.HostTitle = 'PDF Merger';
host.ResizeMode = ResizeMode.NoResize;
host.Show();
host.Width = 370;
host.Height = 400;


The CreateWindow() function contains a WrapPanel with buttons, labels, textbox and a StatusBar to show some message if needed (it helped me debug).

 

The GO button has OnClick event.

//Button GO
var butGO= new Button();
butGO.Margin = new Thickness(10, 0, 10, 0);
butGO.Content = "GO !";
butGO.Width = "40";
butGO.Background = new SolidColorBrush(Colors.Blue);
butGO.Foreground = new SolidColorBrush(Colors.White);
wrapPanel.Children.Add(butGO);
butGO.add_Click(OnClickbutGO);

 

We monitor at least that :
– the delivery number exist in the system (MIWorker MWS410MI/GetHead)
– the email address of the user exist in CRS111 type 04 (MIWorker CRS111MI/Get)
– the user has selected at least one document (check property IsChecked on checkboxes)

public function OnClickbutGO(sender: Object, e: RoutedEventArgs) 
{
   
   if(cb1.IsChecked == false && cb2.IsChecked == false && cb3.IsChecked == false)
   {
           ConfirmDialog.ShowErrorDialogWithoutCancel("Select at least one document.");     
   }
   else if(EMAIL == '')
   {
        ConfirmDialog.ShowErrorDialogWithoutCancel("Email address missing in M3.","Check CRS111 type 04 and relaunch the widget.");
   }
   else if(dlixBox.Text != '')
   {
           //check DLIX value
        try
        {
          var record = new MIRecord();
          record["CONO"] = wCONO;
          record["DLIX"] = dlixBox.Text;
          
          MIWorker.Run("MWS410MI", "GetHead", record, OnRunCompletedMWS410MI);

        }
         catch (ex)
        {
          debug.WriteLine("ex : "+ex);
        }
   }
   else
   {
           ConfirmDialog.ShowErrorDialogWithoutCancel("DLIX number is mandatory.");
   }
   dlixBox.Focus();
}

… and if everything is OK then we trigger the MEC mapping by dropping an XML file into the proper directory.

 

The XML file is built by using  the XmlWriter (System.Xml).

I chose a classic MBM initiator detection in MEC (MBM_S2_R1) and built the global MBM intiator.

This kind of file can be dropped into the input directory. It’s also possible to go for a channel detection where you can drop a more light xml file with the minimum of tags; in this case, you can drop all the tags used for detection by the partner administrator.

Minimum mandatory data to transfer to MEC is :

  • the XQuery : tag “DocumentVariant”
  • the email address of the user (extracted from CRS111MI) : tag “DocumentNumber”

 

You can change the tags at your convenience!

function buildXMLTrigger() 
{
    var request : String = "(";
    if(cb1.IsChecked == true)  request = request + "/CMR|";
    if(cb2.IsChecked == true)  request = request + "/CustomerInvoice|";
    if(cb3.IsChecked == true)  request = request + "/DeliveryNote|";
    request = request + ")";

    statusbar.AddMessage(EMAIL);
    try
    {
         var xmlWriter = XmlWriter.Create("\\"+"\\YourServer\\PDF_Merger\\trigger"+dlixBox.Text+".xml");

         //MvxEnvelope
         xmlWriter.WriteStartDocument();
         xmlWriter.WriteStartElement("MvxEnvelope");
            //SubmitterID
            xmlWriter.WriteStartElement("SubmitterID");
                  xmlWriter.WriteStartElement("Host");
                  xmlWriter.WriteString("GGM3");
                  xmlWriter.WriteEndElement();

                  xmlWriter.WriteStartElement("EnvironmentID");
                  xmlWriter.WriteString("M3FDBPRD");
                  xmlWriter.WriteEndElement();

                  xmlWriter.WriteStartElement("Program");
                  xmlWriter.WriteString("PDF_MERGER");
                  xmlWriter.WriteEndElement();
            xmlWriter.WriteEndElement();//SubmitterID

            //Target
            xmlWriter.WriteStartElement("Target");
               //e-collaborator
               xmlWriter.WriteStartElement("e-collaborator");
                  //Sender
                  xmlWriter.WriteStartElement("Sender");
                     //Reference1
                     xmlWriter.WriteStartElement("Reference1");
                        //Data
                        xmlWriter.WriteStartElement("Data");
                        xmlWriter.WriteString(UserContext.CurrentCompany);
                        xmlWriter.WriteEndElement();//Data
                     xmlWriter.WriteEndElement();//Reference1
                     //Reference2
                     xmlWriter.WriteStartElement("Reference2");
                        //Data
                        xmlWriter.WriteStartElement("Data");
                        xmlWriter.WriteString(UserContext.CurrentDivision);
                        xmlWriter.WriteEndElement();//Data
                     xmlWriter.WriteEndElement();//Reference2    
                  xmlWriter.WriteEndElement();//Sender

                  //Recipients
                  xmlWriter.WriteStartElement("Recipients");
                     //Recipient
                     xmlWriter.WriteStartElement("Recipient");
                        //Reference1
                        xmlWriter.WriteStartElement("Reference1");
                           //Data
                           xmlWriter.WriteStartElement("Data");
                           xmlWriter.WriteString("PDF_MERGER");
                           xmlWriter.WriteEndElement();//Data
                        xmlWriter.WriteEndElement();//Reference1
                     xmlWriter.WriteEndElement();//Recipient    
                  xmlWriter.WriteEndElement();//Recipients

               xmlWriter.WriteEndElement();//e-collaborator
            xmlWriter.WriteEndElement();//Target
            //MvxBody   
            xmlWriter.WriteStartElement("MvxBody");
                  //MovexBusinessMessageInitiator
                  xmlWriter.WriteStartElement("MovexBusinessMessageInitiator");

                     xmlWriter.WriteStartElement("DocumentNumber");
                     xmlWriter.WriteString(EMAIL);
                     xmlWriter.WriteEndElement();

                     xmlWriter.WriteStartElement("DocumentVariant");
                     xmlWriter.WriteString(request+"[@M3_DLIX="+dlixBox.Text+"]");
                     xmlWriter.WriteEndElement();

                     //MessageKeys
                     xmlWriter.WriteStartElement("MessageKeys");
                        //CONO
                        xmlWriter.WriteStartElement("MessageKey1");
                           xmlWriter.WriteStartElement("Field");
                           xmlWriter.WriteString("CONO");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(UserContext.CurrentCompany);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                        //DIVI
                        xmlWriter.WriteStartElement("MessageKey2");
                           xmlWriter.WriteStartElement("Name");
                           xmlWriter.WriteString("DIVI");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(UserContext.CurrentDivision);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                        //DLIX
                        xmlWriter.WriteStartElement("MessageKey3");
                           xmlWriter.WriteStartElement("Name");
                           xmlWriter.WriteString("DLIX");
                           xmlWriter.WriteEndElement();
                           xmlWriter.WriteStartElement("Value");
                           xmlWriter.WriteString(dlixBox.Text);
                           xmlWriter.WriteEndElement();
                        xmlWriter.WriteEndElement();
                     xmlWriter.WriteEndElement();

                  xmlWriter.WriteEndElement();//MovexBusinessMessageInitiator
         
            xmlWriter.WriteEndElement();//MvxBody
         xmlWriter.WriteEndDocument();//MvxEnvelope

         xmlWriter.Close();
       }
       catch(ex)
       {
            statusbar.AddMessage(ex);
       }
}

 

MEC mapping

Some .jar are required to read the IDM database and to merge the .pdf files. We already have icp.jar at our disposal.

A quick search on a famous search engine and we find the wonderful and free (!)  pdfbox-app-2.0.2.jar that will do the job for us.

https://pdfbox.apache.org/download.cgi

In the ION mapper, import those .jar to the library :

 

The mapping is reduced the following one :

  • one function to retrieve the urls of all the .pdf files based on the user’s document selection + build the report in HTML
try 
{
            // Create and connect the connection
            com.infor.daf.icp.Connection conn = new com.infor.daf.icp.Connection("https://yourServer:20108/ca/", "login", "password", com.infor.daf.icp.Connection.AuthenticationMode.BASIC);
            conn.connect();
            
            String FlagFirst = "1";
            String docType = "";
            
            org.apache.pdfbox.multipdf.PDFMergerUtility ut = new org.apache.pdfbox.multipdf.PDFMergerUtility();
            
            // Execute an XQuery search and print the display name for all items 
            com.infor.daf.icp.CMItems items = com.infor.daf.icp.CMItems.search(conn, DocumentVariant, 0, 100);
            
            
            for(com.infor.daf.icp.CMItem item : items) 
            {
                for(com.infor.daf.icp.CMResource res : item.getResources().values()) 
                {
                    
                    if(res.getMimeType().equals("application/pdf"))
                    {
                        if(FlagFirst.equals("1"))
                        {
                            
                            String InitHTML = "<h1>PDF Merger report</h1><TABLE COLS=\"3\" FRAME=\"ALL\" BORDER-COLOR=\"Black\" BORDER=\"1\" VALIGN TD=\"MIDDLE\">";
                            String Titles = "<tr><th BGCOLOR=\"Navy\"><font COLOR=\"WHITE\">Doc type</font></th><th BGCOLOR=\"Navy\" <font COLOR=\"WHITE\">Doc name</font></th><th BGCOLOR=\"Navy\" <font COLOR=\"WHITE\">Version</font></th></tr>";
                            
                            TEXT = InitHTML + Titles;
                            
                            FlagFirst = "0";
                        }
                        if(res.getFilename().contains("MMS480PF")) docType = "Packing list";
                        if(res.getFilename().contains("MWS610PF")) docType = "CMR";
                        if(res.getFilename().contains("OIS199PF")) docType = "Cust. invoice";
                        
                        TEXT = TEXT + "<tr><td ALIGN =\"MIDDLE\">"+docType+"</td><td ALIGN =\"MIDDLE\">" +res.getFilename()+ "</td><td ALIGN =\"MIDDLE\">"+item.getVersion()+"</td></tr>";
                        
                        ut.addSource(res.getUrlStream());  
                    }
                }
                System.out.println("\n NodeName : " + item.getNodeName());
            }
            if(FlagFirst.equals("0"))
            {
                TEXT = TEXT + "</TABLE>";
            }
            
            ut.setDestinationFileName(getManifestInfo("agr:pathFrom")+"Docs_"+iValue+".pdf");
            fileAttachment = getManifestInfo("agr:pathFrom")+"Docs_"+iValue+".pdf";
            ut.mergeDocuments();
            
            conn.disconnect();
        } 
        catch(Exception e) 
        {
            e.printStackTrace();
        }
        
        EMAIL = DocumentNumber;
        DLIX = iValue;
  • one function to send an email with attachment
try
        {
            // SMTP
            java.util.Properties props = new java.util.Properties();
            props.put("mail.smtp.host", constMailServer);

            // session
            javax.mail.Session session = javax.mail.Session.getDefaultInstance(props, null);

            javax.mail.internet.InternetAddress addressFrom = new javax.mail.internet.InternetAddress(constFrom);
            

            // Create msg
            javax.mail.Message msg = new javax.mail.internet.MimeMessage(session);
            
            msg.setFrom(addressFrom);
            
            javax.mail.internet.InternetAddress addressTo[] = javax.mail.internet.InternetAddress.parse(EMAIL);
            msg.setRecipients(javax.mail.Message.RecipientType.TO, addressTo);
            msg.setSubject("PDF MERGED documents for delivery "+DLIX);
            
            javax.mail.internet.MimeBodyPart messageBodyPart = new javax.mail.internet.MimeBodyPart();
            messageBodyPart.setContent(TEXT,"text/html");
            javax.mail.Multipart multipart = new javax.mail.internet.MimeMultipart();
            multipart.addBodyPart(messageBodyPart);
            
            messageBodyPart = new javax.mail.internet.MimeBodyPart();
            javax.activation.DataSource source = new javax.activation.FileDataSource(fileAttachment);
            messageBodyPart.setDataHandler(new javax.activation.DataHandler(source));
            messageBodyPart.setFileName("Documents_"+DLIX+".pdf");
            multipart.addBodyPart(messageBodyPart);
            
            msg.setContent(multipart);
            
            javax.mail.Transport.send(msg);
        }
        catch(javax.mail.MessagingException e)
        {
            throw new MeCError(e.getMessage());
        }

 

 

Last but not the least, the final result in a screenshot :

Fig11

The attachment’s name contains the delivery number and the report displays all the .pdf retrieved from IDM.

Note : there are 3 invoices in the delivery (according to customer settings), 1 packing list and 1 CMR (with a duplicate…)

 

That’s it! Happy coding !

 

Maxime.

 

2 thoughts on “PDF Merger – a starting point”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s