Code to Print Multiple AR Invoices

Let’s say I wanted to write a BPM that, when triggered, prints out the list of invoices fed to it.
How would I get it to do it all in one instead of it sending each individual form to the task agent?

I don’t see a parameter for, say InvcList; only for an individual invoice.
When you’re in AR Invoice Entry and your print the group, how does that work?

1 Like

I have a form customization I wrote to cycle through a list of invoices and print each one, if you’re interested.
Haven’t done it for BPM though.

Epicor, if you’re listening, it seems a ton of clients have a mass print AR invoice customization to perform the function described in these many threads. Maybe a out-of-the-box solution should be included to perform this action? :slight_smile:

I can get it to cycle through and print them all, but they all get an individual task in the system monitor whereas printing from a group only yields one task.
My current method bogs down the print server if there are a load of them to print.

I’m interested in looking at how the range print works, because I believe it probably does what you’re needing it to do rather than creating a new task per report.

That’s what I’m trying to figure out; how to print a range.
The parameter given only suggests I can give it a single invoice.
I’m also not familiar with the code to do it through a BPM. I only have snippets of code that prints via the UI.

I did a trace on the Mass Print AR Invoices report (Financial > AR > Reports)
and it looks like with that you have to at least past a list of CustNums and a start/end invoice number:

<parameter name="ds" type="Erp.Rpt.ARInvFormDataSet">
  <ARInvFormDataSet xmlns="http://www.epicor.com/Ice/300/Rpt/ARInvForm/ARInvForm">
    <ARInvFormParam>
      <InvoiceNum>0</InvoiceNum>
      <CurGroup></CurGroup>
      <PrintNPost>false</PrintNPost>
      <Vouchering>false</Vouchering>
      <AssignLegalNumber>false</AssignLegalNumber>
      <Description></Description>
      <DocTypeOption>All</DocTypeOption>
      <EnableAssignLegalNumber>false</EnableAssignLegalNumber>
      <PrintStatusOption>All</PrintStatusOption>
      <TranDocTypeID></TranDocTypeID>
      <TransOptions>ARC</TransOptions>
      <CustList>3~52~4~831~725~5~6~7~8~933~9~12~14~842~541~64~564~565~721~566~567~568</CustList>
      <DCList></DCList>
      <TH_IsService>false</TH_IsService>
      <ARInvFormReportID></ARInvFormReportID>
      <BeginNum>15061</BeginNum>
      <EndNum>15070</EndNum>
      <InvoiceStatusOption>A</InvoiceStatusOption>
      <THHeadOfficeOrBranch></THHeadOfficeOrBranch>
      <THPlaceOfBusiness></THPlaceOfBusiness>

@Rick_Bird, that’s getting closer! The problem I would run into now is the invoices I need to print are not sequential.

good morning.
i am wondering how did this end up?

Morning Al.

Unfortunately, it didn’t.
It kind of died at that point.

thanks for the reply
i,m working on a similar issue - and will post up if we get it done
update…
been slightly delayed - but will get there

1 Like

Just curious if anyone has figured out a slick way to do this yet. I am working on replacing a legacy system that allows users to select any number of invoices for a customer on a web page and print them with the click of a button.

I have found that running a separate REST call to regenerate each invoice individually takes an unacceptable amount of time when there are more than say 10 invoices.

We found a while back the standard menu selection “Mass Print AR Invoices” - which is associated with Demand Mgmt / EDI processing does the trick for us.
It does work to ranges of invoices - in that we can easily tie a numeric range to an AR batch.

1 Like

Ok thanks

You guys could ask @Aaron_Moreng

I remember having a screenshot with him doing something like:

Aaron can you elaborate on your technique? Adapter, comma delimited list? or multiple calls to the BO?

1 Like

I leveraged the native EpiDataView “ReportParam” inside Mass Print AR Invoices to submit each invoice number in the list to the task agent quickly. The only problem I’ve had with this approach is that if they are submitted too quickly, there has been instances where the report header does not match the report details, resulting in some really wonky invoices. To solve, I put added Thread.Sleep(500) inside my foreach loop and that has served me well.

// **************************************************
/// Custom code for ARInvForm
// Created: 5/20/2016 3:08:40 PM
// **************************************************
using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Erp.UI;
using Ice.Lib.Customization;
using Ice.Lib.ExtendedProps;
using Ice.Lib.Framework;
using Ice.Lib.Searches;
using Ice.UI.FormFunctions;
using System.Text.RegularExpressions;
using System.Threading;

public class Script
{
	// ** Wizard Insert Location - Do Not Remove 'Begin/End Wizard Added Module Level Variables' Comments! **
	// Begin Wizard Added Module Level Variables **

	private EpiDataView edvReportParam;
	// End Wizard Added Module Level Variables **

	// Add Custom Module Level Variables Here **
	DataTable dtInput = new DataTable();
	string Invoice;
	bool printed = false;
	public void InitializeCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Variable Initialization' lines **
		// Begin Wizard Added Variable Initialization

		this.edvReportParam = ((EpiDataView)(this.oTrans.EpiDataViews["ReportParam"]));
		this.edvReportParam.EpiViewNotification += new EpiViewNotification(this.edvReportParam_EpiViewNotification);
		// End Wizard Added Variable Initialization

		// Begin Wizard Added Custom Method Calls

		this.btnAdd.Click += new System.EventHandler(this.btnAdd_Click);
		this.btnRemove.Click += new System.EventHandler(this.btnRemove_Click);
		this.btnPrint.Click += new System.EventHandler(this.btnPrint_Click);
		this.txtAddInvoice.Validated += new System.EventHandler(this.txtAddInvoice_Validated);
		this.btnClear.Click += new System.EventHandler(this.btnClear_Click);
		// End Wizard Added Custom Method Calls
		dtInput.Columns.Add("Invoice",typeof(int));
		dtInput.Columns.Add("Printed", typeof(bool));
	}

	public void DestroyCustomCode()
	{
		// ** Wizard Insert Location - Do not delete 'Begin/End Wizard Added Object Disposal' lines **
		// Begin Wizard Added Object Disposal

		this.edvReportParam.EpiViewNotification -= new EpiViewNotification(this.edvReportParam_EpiViewNotification);
		this.edvReportParam = null;
		this.btnAdd.Click -= new System.EventHandler(this.btnAdd_Click);
		this.btnRemove.Click -= new System.EventHandler(this.btnRemove_Click);
		this.btnPrint.Click -= new System.EventHandler(this.btnPrint_Click);
		this.txtAddInvoice.Validated -= new System.EventHandler(this.txtAddInvoice_Validated);
		this.btnClear.Click -= new System.EventHandler(this.btnClear_Click);
		// End Wizard Added Object Disposal

		// Begin Custom Code Disposal

		// End Custom Code Disposal
	}

	private void edvReportParam_EpiViewNotification(EpiDataView view, EpiNotifyArgs args)
	{
		// ** Argument Properties and Uses **
		// view.dataView[args.Row]["FieldName"]
		// args.Row, args.Column, args.Sender, args.NotifyType
		// NotifyType.Initialize, NotifyType.AddRow, NotifyType.DeleteRow, NotifyType.InitLastView, NotifyType.InitAndResetTreeNodes
		if ((args.NotifyType == EpiTransaction.NotifyType.Initialize))
		{
			if ((args.Row > -1))
			{
			//MessageBox.Show(edvReportParam.dataView[edvReportParam.Row]["InvoiceNum"].ToString());
			}
		}
	}
	

	private void ARInvForm_Load(object sender, EventArgs args)
	{
		// Add Event Handler Code
		EpiDataView dvRP = (EpiDataView)oTrans.EpiDataViews["ReportParam"];
		//MessageBox.Show(dvRP.dataView[dvRP.Row]["InvoiceNum"].ToString());
		dtInput.Clear();
		grdInput.DataSource = dtInput;
		//EpiDataView dvRP = (EpiDataView)oTrans.EpiDataViews["ReportParam"];
		Invoice = dvRP.dataView[dvRP.Row]["InvoiceNum"].ToString();	
		if (Invoice != "0")
		{
			dtInput.Rows.Add(Invoice, printed);
		}
	}

	private void ReadOnlyDT()
	{
	foreach (DataColumn dc in dtInput.Columns)
		{
		//dc.ReadOnly = true;
		}
	}
	
	private void btnAdd_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		//Invoice = txtAddInvoice.Value.ToString();
		if (String.IsNullOrEmpty(txtAddInvoice.Text)) return;
		Invoice = txtAddInvoice.Value.ToString();
		//regex
		Regex regex = new Regex(@"^[0-9]{6}$");
		Match match = regex.Match(Invoice);
		if (match.Success)	
		{
			dtInput.Rows.Add(Invoice, printed);
		}
		else
		{
			MessageBox.Show("Enter a 6 digit invoice number");
		}
		txtAddInvoice.Clear();
		txtAddInvoice.Focus();
	}

	private void btnRemove_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **	
		
		if (String.IsNullOrEmpty(txtAddInvoice.Text)) return;
		Invoice = txtAddInvoice.Value.ToString();					
		for(int i = dtInput.Rows.Count-1;i>=0;i--)//backwards loop 
		{
			DataRow dr = dtInput.Rows[i];
				if (dr["Invoice"].ToString() == Invoice)
				{
					dr.Delete();//will remove any duplicate entries, so be careful
				} 
		}	
		grdInput.Refresh();
		txtAddInvoice.Clear();
	}

	private void btnPrint_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		txtNumberInvoicesSent.Value = 0;
		if (dtInput.Rows.Count<1){ MessageBox.Show("No Invoices Selected for Print"); 
		return;}
		EpiDataView dvRP = (EpiDataView)oTrans.EpiDataViews["ReportParam"];
		string workID = oTrans.WorkStationID.ToString();
		try {
			foreach (DataRow dr in dtInput.Rows)
			{
				dvRP.dataView[dvRP.Row]["InvoiceNum"] = dr["Invoice"];
				dvRP.dataView[dvRP.Row]["AutoAction"] = "SSRSPREVIEW";
				dvRP.dataView[dvRP.Row]["WorkstationID"] = workID;
				dr["Printed"] = true;
				oTrans.Update();					
				oTrans.SubmitToAgent("SystemTaskAgent", 0, 0);
				Thread.Sleep(500);
			}
			
			txtNumberInvoicesSent.Value = dtInput.Rows.Count;
			oTrans.PushDisposableStatusText("Reports Submitted for Preview...", true);
		}
		catch (Exception ex)
		{
		MessageBox.Show(ex.Message);
		}
	}

	private void txtAddInvoice_Validated(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **		
	}

	private void btnClear_Click(object sender, System.EventArgs args)
	{
		// ** Place Event Handling Code Here **
		dtInput.Clear();
		txtNumberInvoicesSent.Clear();
	}
}

Rpt.ARInvForm.ARInvForm_Customization_MassPrintSelectInvoice_CustomExport.xml (40.8 KB)

1 Like

If you have APM - you could create a Break Routing, and break on UD field added to the InvcHead table. That UD field would be updated by a dashboard used to select the invoices to print. A Condition widget in the Break/Routing could be used to filter out the invoices you didn’t select

Then in the BPM’s autoprint widget set:

  • the CustList, as all the CustNums (ex: "1~33~5661~4490")
  • the BeginNum to the MIN of invoices selected.
  • the EndNum to the MAX of invoices selected.

While the above selects any invoice in the range BeginNum to EndNum AND with the custnum in the CustList,(which includes Invoice you don’t want), the break rout would break it into different printjobs based on the UD filed. And a condition widget could throw away the ones not to be printed.

The following makes one print task,

image

but generates two separate reports.

image

Changing the widget connected to the FALSE output of the condition, to a Gen only shows that the “unselected” were only generated and not printed.

image

Have nothing connected to the FALSE, and you’ll only get the invoices you selected.

1 Like

And if you don’t want to update the InvcHead table with a UD field. Use a UD table to hold the desired invoice numbers. Then add it to the ARForm RDD, with a definition relationsship.

And that shouldn’t even need the Break Routing. The RDD output should be limited to the invoices in the UD table.

Thanks for the ideas everyone. I think I’m going to adjust my REST call to pass the parameters in using a date range, invoice status option, and customer list. I wish there were a way to choose just pass in an invoice list since I would like the flexibility to merge the invoices the user selects into one PDF but this will suffice.

Sooooo, have I ever mentioned Epicor Functions? :slight_smile:

Create a function that accepts a json string in a dataset format. Something like:

{
  "Invoices": [
    {
      "id": 100000
    },
    {
      "id": 100001
    }
  ]
}

Add a Reference to Newtonsoft.Json.

image

Create a function that accepts the Json string.

Make sure to add a using for the Newtonsoft library:

image

Declare a DataSet variable to hold your invoices:

Add the one line of code that deserializes your Json into the DataSet variable.

Now you have your invoice numbers in the dataset, so you can use @Aaron_Moreng’s code to do the actual printing.

			foreach (DataRow dr in dtInput.Rows)
			{
				dvRP.dataView[dvRP.Row]["InvoiceNum"] = dr["Invoice"];
				dvRP.dataView[dvRP.Row]["AutoAction"] = "SSRSPREVIEW";
				dvRP.dataView[dvRP.Row]["WorkstationID"] = workID;
				dr["Printed"] = true;
				oTrans.Update();					
				oTrans.SubmitToAgent("SystemTaskAgent", 0, 0);
				Thread.Sleep(500);
			}

You would need to add the references to do the SubmitToAgent instead of the oTrans but that’s left as an exercise for the printer, er, reader. Of course, 10.2.500+ required…

The tricky part is if you need to retrieve the document locally and not using APR or something to send them directly from the TaskAgent. One problem at a time…

Mark W.

9 Likes

To follow up on Marks post, If you wanted to retrieve the document locally using REST, this could easily be accomplished using a BAQ.

Simply stated, Epicor stores the document blob in the DB once generated in the table SysRptLst
Field: [SysRptLst].[RptData]

This is the report bytes so if you run this BAQ from REST you’ll get a base64encoded bytestream which can / should be able to easily turn into a PDF document.

As a test you could run the BAQ and paste the output of the RptData (base64 encoded) here

and it should generate your PDF. You can do the same in code with javascript (or whatever language of your choice)

5 Likes