Adding the Payment Advice to AX 2012 Print Management
In AX 2012 Print Management, the Payment Advice is not a supported document type out of the box. We recently had a customer request to add this feature so that their vendors could receive the Payment Advice via email. This blog describes the changes needed to meet this requirement. Adding the Payment Advice to AX 2012 Print Management is only one part of the solution. Because the Payment Advice report is designed to print all vendors at once, rather than running the report for each vendor separately, modifications must be made to ensure that each vendor receives a separate pdf file with only his own Payment Advice.
It is not the intention of this blog to rehash the detailed information in the AX 2012 Print Management Integration Guide. Instead, I wanted to expand on the whitepaper with the specific code needed for the Payment Advice. With this in mind, I will not go into a detailed explanation of every class and method, but will only highlight a few important details.
First, it may be helpful to envision the hierarchy as you work through this code.
For the payment advice, we want users to be able to set the preferences at the document level and at the vendor level, so the hierarchy looks like this…
Purch (accounts payable node)
VendTable (vendor node)
BankPaymAdvice (new doc node)
The following existing AX objects were modified for this feature:
Enums:
PrintMgmtNodeType - Added BankPaymAdvice
PrintMgmtDocumentType - Added BankPaymAdvice
Tables:
BankPaymAdviceVendTemp - Added field VendAccount, Added Relation VendTable
PrintMgmtReportFormat - Methods\populate – modified to support the new doc type
Classes:
PrintMgmtNode - Methods\construct – modified for the new node type
PrintMgmtNode_Purch - Methods\getDocumentTypes – modified for the new doc type
PrintMgmtNode_VendTable - Methods\getDocumentTypes – modified for the new doc type
PrintMgmtHierarchy_Purch:
Methods\getNodesImplementation – add BankPaymAdvice as a supported node
Methods\getParentImplementation – add support for BankPaymAdvice (see Objects of Interest)
PrintMgmtDocType:
Methods\getDefaultReportFormat – return the report name for BankPaymAdvice
Methods\getQueryTableId– modified to add the BankPaymAdviceVendTmp table for BankPaymAdvice
VendOutPaym_NACHA - Add the Use Print Management field to the dialog and pass along to the controller class
Methods modified:
ClassDeclaration
Dialog
getFromDialog
init
printPaymAdvice
unpack
The follow custom objects were created for this feature:
Classes:
PrintMgmtNode_BankPaymAdvice – supporting class for the new node type
FDBankPaymAdviceVendController – controller class for the report. Extends SrsPrintMgmtController.
All of the code changes are included below:
Table: PrintMgmtReportFormat
Method\Populate: added the BankPaymAdvice doc type
…
addAX(PrintMgmtDocumentType::PurchRFQ);
addAX(PrintMgmtDocumentType::PurchRFQAccept);
addAX(PrintMgmtDocumentType::PurchRFQReject);
addAX(PrintMgmtDocumentType::PurchRFQReturn);
addAX(PrintMgmtDocumentType::PurchaseOrderConfirmationRequest);
//Stoneridge Software Begin
addAX(PrintMgmtDocumentType::BankPaymAdvice);
//Stoneridge Software End
…
New Classes:
The out of box report uses a controller class that extends SRSReportRunController. This new class is basically a modified copy of BankPaymAdviceVendController extending SrsPrintMgmtController. The entire class is included here.
public class FDBankPaymAdviceVendController extends SrsPrintMgmtController
{
VendOutPaym vendOutPaym;
List vendOutPaymList;
str paymRef;
VendTrans vendTransOriginalInvoice;
BankPaymAdviceVendTmp bankPaymAdviceVendTmp;
CompanyInfo companyInfo;
VendTable vendTable;
LedgerJournalTrans ledgerJournalTrans;
SpecTrans specTrans;
VendTransOpen vendTransOpen;
VendTrans vendTrans;
Common transtable;
UsePrintMgmt paymAdvUsePrintMgmt;
str newPaymRefLable;
Amount settledAmount;
CurrencyCode settledCurrencyCode;
str groupBy;
int currentSessionId;
PrintCopyOriginal printCopyOriginal;
LogisticsAddressing companyAddress;
#define.ReportName('BankPaymAdviceVend.Report')
}
private str currentSessionId()
{
return int2str(currentSessionId);
}
private VendCashDiscAmount getCashDiscAmount()
{
// gets the cash discount that is specified when settling an invoice.
VendTransCashDisc vendTransCashDisc;
changecompany(vendTransOpen.company())
{
vendTransCashDisc = VendTransCashDisc::findByUseCashDisc(vendTransOpen.TableId,
vendTransOpen.RecId,
vendTransOpen.UseCashDisc,
ledgerJournalTrans.TransDate);
}
return -vendTransCashDisc.CashDiscAmount;
}
protected void initPrintMgmtReportRun()
{
paymAdvUsePrintMgmt = this.parmArgs().parmEnum();
printMgmtReportRun=PrintMgmtReportRun::construct(
PrintMgmtHierarchyType::Purch,PrintMgmtNodeType::BankPaymAdvice,PrintMgmtDocumentType::BankPaymAdvice);
printMgmtReportRun.parmForcePrintJobSettings(!paymAdvUsePrintMgmt);
printMgmtReportRun.parmReportRunController(this);
}
private void insertBankPaymAdviceVendTmp(boolean isSettledVoucherAmount,
boolean isRedrawPayments)
{
Blobdata image;
bankPaymAdviceVendTmp.CompanyPhone = companyInfo.phone();
bankPaymAdviceVendTmp.CompanyTeleFax = companyInfo.teleFax();
bankPaymAdviceVendTmp.CompanyGiro = companyInfo.Giro;
bankPaymAdviceVendTmp.CompanyCoRegNum = companyInfo.CoRegNum;
bankPaymAdviceVendTmp.CompanyAddress = companyAddress;
bankPaymAdviceVendTmp.VendName = DirPartyTable::findRec(vendTable.Party).Name;
bankPaymAdviceVendTmp.VendAddress = LogisticsPostalAddress::addressFromRecId(vendTrans.RemittanceAddress);
bankPaymAdviceVendTmp.PaymAdviceDate = systemDateGet();
bankPaymAdviceVendTmp.PaymentReference = paymref;
BankPaymAdviceVendTmp.VendAccount = VendTable.AccountNum;
if (newPaymRefLable)
{
bankPaymAdviceVendTmp.PaymRefLabel = newPaymRefLable;
}
if (!isRedrawPayments)
{
bankPaymAdviceVendTmp.Invoice = vendTrans.Invoice;
bankPaymAdviceVendTmp.InvoiceDate = vendTrans.TransDate;
bankPaymAdviceVendTmp.CurrencyCode = vendTrans.CurrencyCode;
bankPaymAdviceVendTmp.AmountCur = vendTrans.AmountCur;
bankPaymAdviceVendTmp.CashDisc = this.getCashDiscAmount();
bankPaymAdviceVendTmp.Balance01 = -specTrans.Balance01;
bankPaymAdviceVendTmp.TransDate = ledgerJournalTrans.TransDate;
if (isSettledVoucherAmount)
{
bankPaymAdviceVendTmp.SettledAmount = settledAmount;
}
}
else
{
bankPaymAdviceVendTmp.Invoice = vendTransOriginalInvoice.Invoice;
bankPaymAdviceVendTmp.InvoiceDate = vendTransOriginalInvoice.TransDate;
bankPaymAdviceVendTmp.CurrencyCode = vendTransOriginalInvoice.CurrencyCode;
bankPaymAdviceVendTmp.AmountCur = vendTransOriginalInvoice.AmountCur;
bankPaymAdviceVendTmp.CashDisc = 0; //we only give the discount once
bankPaymAdviceVendTmp.Balance01 = this.paymentAmountCur();
bankPaymAdviceVendTmp.TransDate = ledgerJournalTrans.TransDate;
if (isSettledVoucherAmount)
{
bankPaymAdviceVendTmp.SettledAmount = -settledAmount;
}
}
bankPaymAdviceVendTmp.SessionId = currentSessionId;
bankPaymAdviceVendTmp.Grouping = groupBy;
bankPaymAdviceVendTmp.SettledCurrencyCode = settledCurrencyCode;
image = CompanyImage::find(companyInfo.dataAreaId, tablenum(CompanyInfo), companyInfo.RecId).Image;
bankPaymAdviceVendTmp.Image = image;
if (image)
{
bankPaymAdviceVendTmp.PrintImage = NoYesCombo::Yes;
}
else
{
bankPaymAdviceVendTmp.PrintImage = NoYesCombo::No;
}
bankPaymAdviceVendTmp.insert();
}
private AmountCur paymentAmountCur()
{
CustVendTransDetails vendTransDetails = new CustVendTransDetails(vendTransOriginalInvoice);
vendTransDetails.setCustVendTrans(vendTransOriginalInvoice);
return (vendTransOriginalInvoice.AmountCur - vendTransDetails.utilizedCashDisc());
}
protected void preRunModifyContract()
{
Query reportQuery;
reportQuery = this.parmReportContract().parmQueryContracts().lookup(this.getFirstQueryContractKey());
SrsReportHelper::addParameterValueRangeToQuery(
reportQuery,
tableNum(BankPaymAdviceVendTmp),
fieldNum(BankPaymAdviceVendTmp,SessionId),
this.currentSessionId());
}
public void processReport(List paramOutPaymlist)
{
boolean ret = false;
boolean calcSum;
VendSettlement vendSettlement;
VendPromissoryNoteInvoice vendPromissoryNoteInvoice;
TaxWithholdTrans taxWithholdTrans;
ListEnumerator li;
VendOutPaymRecord vendOutPaymRecord;
companyInfo = CompanyInfo::find();
companyAddress = companyInfo.postalAddress().Address;
currentSessionId = new xSession().sessionId();
//Delete the previous records if there are existing record corresponding to the current session or there are existing records
//more than two days old.
delete_from bankPaymAdviceVendTmp where ((bankPaymAdviceVendTmp.SessionId == currentSessionId) ||
(bankPaymAdviceVendTmp.DateOfCreation < today() - 2));
li = paramOutPaymlist.getEnumerator();
while (li.moveNext())
{
vendOutPaymRecord = li.current();
ledgerJournalTrans = vendOutPaymRecord.parmCustVendPaym().ledgerJournalTrans();
vendTable = VendTable::findByCompany(vendOutPaymRecord.parmCustVendPaym().ledgerJournalTrans().Company,
vendOutPaymRecord.parmCustVendPaym().ledgerJournalTrans().parmAccount());
this.setPaymReferenceStr();
calcSum = true;
// print a line for each SpecTrans record for the journal line
while select crosscompany specTrans
where specTrans.SpecRecId == ledgerJournalTrans.RecId &&
specTrans.SpecCompany == ledgerJournalTrans.company() &&
specTrans.SpecTableId == tableNum(LedgerJournalTrans) &&
specTrans.RefTableId == tableNum(VendTransOpen)
{
// The report prints from three global table buffers so we need to populate
// the remaining two prior to sending them to the report for printing
vendTransOpen = specTrans.vendTransOpen();
vendTrans = vendTransOpen.vendTrans();
// Remove tax from the payment amount
changecompany (vendTrans.company())
{
select sum(InvoiceTaxWithholdAmount) from taxWithholdTrans
where taxWithholdTrans.JournalNum == ledgerJournalTrans.JournalNum &&
taxWithholdTrans.Invoice == vendTrans.Invoice;
//vendTrans.AmountCur and spectrans.Balance01 are negative numbers while taxes are postive
vendTrans.AmountCur += taxWithholdTrans.InvoiceTaxWithholdAmount;
specTrans.Balance01 += taxWithholdTrans.InvoiceTaxWithholdAmount;
}
ret = true;
if (calcSum)
{
settledAmount = ledgerJournalTrans.amount();
settledCurrencyCode =ledgerJournalTrans.displayCurrencyCode();
groupBy = vendOutPaymRecord.parmCustVendPaym().ledgerJournalTrans().parmAccount() + settledCurrencyCode;
calcSum = false;
this.insertBankPaymAdviceVendTmp(true,false);
// Sourabh Khosla (Stoneridge Software Inc.) 04/27/2014 FDD:06 (Email Automation) : Start
// Responsible for sending print reports based on the current vendTable account record.
select * from BankPaymAdviceVendTmp
join vendTable where BankPaymAdviceVendTmp.vendAccount == VendTable.accountNum;
{
this.initPrintMgmtReportRun();
printMgmtReportRun.load(BankPaymAdviceVendTmp,BankPaymAdviceVendTmp,CompanyInfo::languageId());
this.outputReports();
}
sleep(1500); // Gives the time to render the report
delete_from BankPaymAdviceVendTmp;
// Sourabh Khosla (Stoneridge Software Inc.) : End
}
else
{
this.insertBankPaymAdviceVendTmp(false,false);
}
}
if (!ret)
{
// this section is run when generating payments from a redraw
// The promissory note has already been settled which is why the noter invoice exists.
// All the data in this select is from the same company and is based on the payment.
while select specTrans
where specTrans.SpecTableId == tableNum(LedgerJournalTrans) &&
specTrans.SpecRecId == ledgerJournalTrans.RecId &&
specTrans.SpecCompany == ledgerJournalTrans.company()
join TransRecId,RecId from vendSettlement
where vendSettlement.RecId == specTrans.RefRecId
join RecId from vendTrans
where vendTrans.RecId == vendSettlement.TransRecId
join PromissoryNoteId,InvoiceCompany,InvoiceVoucher,InvoiceDate,InvoiceId from vendPromissoryNoteInvoice
where vendPromissoryNoteInvoice.PromissoryNoteId == vendTrans.PromissoryNoteID
{
// Here we get the trans settled which may be in a different company and the
// promissory note invoice has the company to look in captured.
changecompany(vendPromissoryNoteInvoice.InvoiceCompany)
{
select firstonly vendTransOriginalInvoice
where vendTransOriginalInvoice.Voucher == vendPromissoryNoteInvoice.InvoiceVoucher &&
vendTransOriginalInvoice.TransDate == vendPromissoryNoteInvoice.InvoiceDate &&
vendTransOriginalInvoice.Invoice == vendPromissoryNoteInvoice.InvoiceId;
}
if (calcSum)
{
settledAmount = ledgerJournalTrans.amount();
settledCurrencyCode =ledgerJournalTrans.displayCurrencyCode();
groupBy = vendOutPaymRecord.parmCustVendPaym().ledgerJournalTrans().parmAccount() + settledCurrencyCode;
calcSum = false;
this.insertBankPaymAdviceVendTmp(true,true);
}
else
{
this.insertBankPaymAdviceVendTmp(false,true);
}
}
}
}
}
protected void runPrintMgmt()
{
vendOutPaym = this.parmArgs().caller();
vendOutPaymList = this.parmArgs().parmObject();
this.processReport(vendOutPaymList);
}
private void setPaymReferenceStr()
{
str tmp;
tmp = ledgerJournalTrans.PaymReference;
if (ledgerJournalTrans.BankChequeNum)
{
tmp = ledgerJournalTrans.BankChequeNum;
newPaymRefLable = "@SYS22495" ;
}
if (ledgerJournalTrans.BankPromissoryNoteNum)
{
tmp = ledgerJournalTrans.BankPromissoryNoteNum;
newPaymRefLable = "@SYS71440" ;
}
if (tmp)
{
if (paymRef != '')
{
paymRef += ',' + tmp;
}
else
{
paymRef = tmp;
}
}
}
public static void main(Args _args)
{
SrsPrintMgmtController controller = new FDBankPaymAdviceVendController();
controller.parmReportName(#ReportName);
controller.parmArgs(_args);
controller.parmShowDialog(false);
controller.startOperation();
}
**********
This is a supporting class for the new node type.
class PrintMgmtNode_BankPaymAdvice extends PrintMgmtNode
{
}
protected str getDisplayCaptionImplementation(Common _tableBuffer)
{
;
return(strfmt("@SYS108944", _tableBuffer.caption()));
}
public List getDocumentTypes()
{
List docTypes;
;
docTypes = new List(Types::Enum);
if (isConfigurationkeyEnabled(configurationkeynum(LogisticsBasic)))
{
docTypes.addEnd(PrintMgmtDocumentType::BankPaymAdvice);
}
return docTypes;
}
public int getIconImageResNum()
{
#resAppl
;
return #ImagePrintManagementTrans;
}
public PrintMgmtNodeType getNodeType()
{
;
return PrintMgmtNodeType::BankPaymAdvice;
}
public RefTableId getReferencedTableId()
{
;
return tablenum(BankPaymAdviceVendTmp);
}
Modified Classes:
class PrintMgmtNode, construct method
...
//Stoneridge Software LL April 2014 Begin
case PrintMgmtNodeType::BankPaymAdvice:
return new PrintMgmtNode_BankPaymAdvice();
//Stoneridge Software LL April 2014 End
...
class PrintMgmtHierarchy_Purch
...
getNodesImplementation
...
//Stoneridge Software LL April 2014 Begin
supportedNodes.addEnd(PrintMgmtNodeType::BankPaymAdvice);
//Stoneridge Software End
...
getParentImplementation
This is an important method for bringing the pieces together. The vendaccount value in the temp table ties back to the parent node (vendTable)
...
//Stoneridge Software LL April 2014 Begin
case PrintMgmtNodeType::BankPaymAdvice:
BankPaymAdviceVendTmp = _nodeInstance.parmReferencedTableBuffer();
if (strlen(BankPaymAdviceVendTmp.VendAccount) != 0)
{
result.parmReferencedTableBuffer(BankPaymAdviceVendTmp.selectRefRecord(fieldnum(BankPaymAdviceVendTmp, VendAccount)));
}
else
{
result.parmReferencedTableBuffer(null);
}
result.parmNodeDefinition(PrintMgmtNode::construct(PrintMgmtNodeType::VendTable));
break;
//Stoneridge Software LL April 2014End
...
class VendOutPaym_NACHA
class Declaration
DialogField paymAdviceUsePrintMgmt; //Stoneridge Software LL April 2014
UsePrintMgmt paymAdvUsePrintMgmt; //Stoneridge Software LL April 2014
Dialog method
...
dialog.addGroup("@SYS54502");
//Stoneridge Software LL April 2014 Begin
paymAdviceUsePrintMgmt = dialog.addfieldvalue(enumStr(NoYes),NoYes::Yes,"@FDA27");
//Stoneridge Software LL April 2014 End
...
getFromDialog method
...
paymAdvUsePrintMgmt = paymAdviceUsePrintMgmt.value();
...
public void printPaymAdvice()
{
Args args = new Args();
args.caller(this);
args.parmObject(this.getOutPaymRecords());
args.parmEnumType(enumNum(NoYes));
args.parmEnum(paymAdvUsePrintMgmt); //Stoneridge Software LL April 2014
new MenuFunction(menuitemOutputStr(BankPaymAdviceVend), MenuItemType::Output).run(args);
}
public boolean unpack(container _packedClass)
{
Version version = RunBase::getVersion(_packedClass);
container base;
boolean ret;
#LOCALMACRO.CurrentListV3
fileName,
printControlReport,
effectiveEntDate
#ENDMACRO
#define.V3(3)
#LOCALMACRO.CurrentListV4
effectiveEntDate,
paymAdvUsePrintMgmt //Stoneridge Software LL April 2014
#ENDMACRO
…
}
class PrintMgmtNode_Purch
getDocumentTypes
...
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderInvoice);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderPackingSlip);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderReceiptsList);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderRequisition);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderConfirmationRequest);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQ);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQAccept);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQReject);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQReturn);
//Stoneridge Software LL March 2014 Begin
docTypes.addEnd(PrintMgmtDocumentType::BankPaymAdvice);
//Stoneridge Software End
...
class PrintMgmtNode_VendTable
…
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderInvoice);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderPackingSlip);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderReceiptsList);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderRequisition);
docTypes.addEnd(PrintMgmtDocumentType::PurchaseOrderConfirmationRequest);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQ);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQAccept);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQReject);
docTypes.addEnd(PrintMgmtDocumentType::PurchRFQReturn);
//Stoneridge Software LL March 2014 Begin
docTypes.addEnd(PrintMgmtDocumentType::BankPaymAdvice);
//Stoneridge Software End
}
…
class PrintMgmtDocType
getDefaultReportFormat()
{
...
case PrintMgmtDocumentType::PurchaseOrderConfirmationRequest:
return ssrsReportStr(PurchPurchaseOrder, Report);
case PrintMgmtDocumentType::PurchRFQ:
case PrintMgmtDocumentType::PurchRFQAccept:
case PrintMgmtDocumentType::PurchRFQReject:
case PrintMgmtDocumentType::PurchRFQReturn:
return ssrsReportStr(RFQSend, Report);
//Stoneridge Software LL March 2014 Begin
case PrintMgmtDocumentType::BankPaymAdvice:
return ssrsReportStr(BankPaymAdviceVend, Report);
//Stoneridge Software End
…
Class PrintMgmtDocType
getQueryTableId()
...
//Stoneridge Software LL March 2014 Begin
case PrintMgmtDocumentType::BankPaymAdvice:
tableId = tableNum(BankPaymAdviceVendTmp);
break;
//Stoneridge Software End
...
Updates to this post:
- You will need to modify the menu item to use the new controller class.
- Also, the new variable for the dialog value “paymAdvUsePrintMgmt” needs to be added to the CurrentList macro in the classDeclaration of VendOutPaym_NACHA.
As noted above, also refer to AX 2012 Print Management Integration Guide for more information on the classes and methods modified here.
Under the terms of this license, you are authorized to share and redistribute the content across various mediums, subject to adherence to the specified conditions: you must provide proper attribution to Stoneridge as the original creator in a manner that does not imply their endorsement of your use, the material is to be utilized solely for non-commercial purposes, and alterations, modifications, or derivative works based on the original material are strictly prohibited.
Responsibility rests with the licensee to ensure that their use of the material does not violate any other rights.