Using one voucher in AX can be risky/tricky at times. There are many examples for that. One of those I will be highlighting here.
It is using One Voucher to post ‘PDC’ for multiple lines of journals. Let’s see how AX treats it.
1) Start by creating a new Customer Payment Journal
.
.
2) Before going into lines of journal, please go to your Journal Name setup and make sure you have selected ONE Voucher.
.
.
3) Go back to journal lines and create payments using PDC. For my example I have created 3 lines of PDC payments, each having different customer number. As you can see in my case the voucher number is same in all three lines. (That’s called One Voucher)
One important point that you may want to take care, is to change session date of your AX, before creating any lines, just because PDC will not allow you to settle it, if it’s not mature. And by default AX takes one day ahead as maturity date, compared to current session date. So better is to change session date to an old value and create lines. You can see in my example, the journal lines are dated as 1st of April. I did this on-purpose, though today is 7th of April.
.
.
4) Now, post your journal. Please note that if you are using Contoso setup, you may be prompted by system for required check#. You can remove that checkbox from PDC (method of payments form) if this is holding you for posting the journal. At the same time make sure you have entered the data in PDC TAB for each journal line.
.
.
5) After your journal is posted, you can have a look of entries that have been created in GL (General Ledger >> Inquiries >> Voucher Transactions); you may filter it by Voucher number as you have got a voucher# from the posted journal lines. It is not must to have a look of this inquiry for posted voucher, but that’s usually a love of financial consultants to have a look of posted voucher; so you can, if you are a financial guru.
6) Now change your session date back to normal (actual today’s date); and navigate to customer post-dated checks (Account Receivables >> Post Dated Checks)
7) Click on ‘Checks to send to bank’ button.
.
.
8) Now identify all your posted transactions (from step#4) and try to "settle clearing entries" for each line of PDC (which were created as result of posting payment journal).
Here is what AX will do:
.
You can watch how One Voucher behaves. This is standard system. No customizations
Reason of failure:
AX voucher/GL framework works on three things.
1) Voucher Number.
2) Posting/Accounting date
3) Data Area ID (Company)
So technically when system tried to settle one PDC, AX failed and showed you error that GL transaction already exists, it’s because AX is trying to find using above references and it cannot differentiate between posted lines using ONE Voucher#. Even if it gets successful in settling 1st line of PDC, it will give you error on remaining PDC(s) that originated from same voucher number. Because for AX there is no difference between three PDC(s).
Yes you are trying to settle one PDC at a time, but AX is looking multiple records for one PDC. Instead of this AX should watch only related record of that PDC and not all records that belong to that voucher number. You can see in my given example the three lines of payment journal belong to 3 different customers. That means one PDC for one customer. You may have a look at prove, that, as you clicked single PDC to settle it, AX is reading three different records. Have a look of following screenshot and compare it with the screenshot of step#3. Ax is reading 3 different lines of posted journal (amount 100, 150, 70)
.
.
For technical lads and lassies
The culprit can be seen under following path for this scenario.
AOT \Classes\CustVendPDCManager\settleClearingTransactions
If you want to debug this, make sure to make some way, because I am sure you remember that AX don’t allows to debug static methods and above method is static. Be smart to duplicate function and remove static keyword as well as server side execution. Nothing will get damaged if you will do this in a duplicate method of the same class. This can make your method debug-able. Finally you can create object of class CustVendPDCManager to call without double colon (::)
If that’s too much of work, I can paste here x++ job that can help to debug this quickly. It’s not my own written piece of code. It’s from Microsoft as provided in class CustVendPDCManager (with some changes to make it debug-able easily)
static void PDCSettleByONVoucher_Example1(Args _args) { LedgerJournalTrans ledgerJournalTrans; GeneralJournalAccountEntry generalJournalAccountEntry, localGeneralJournalAccountEntry; GeneralJournalEntry generalJournalEntry, localGeneralJournalEntry; LedgerEntry ledgerEntry, localLedgerEntry; LedgerEntryJournal ledgerEntryJournal, localLedgerEntryJournal; LedgerTransFurtherPosting ledgerTransFurtherPosting; LedgerJournalEngine ledgerJournalEngine; LedgerJournalTable ledgerJournalTable; LedgerJournalCheckPost ledgerJournalCheckPost; LedgerJournalNameId journalName; LedgerVoucher ledgerVoucher; LedgerVoucherObject ledgerVoucherObject; SubledgerVoucherGeneralJournalEntry subledgerVoucherGeneralJournalEntry; VendTrans vendTrans; TaxWithholdTrans taxWithholdTrans; NumberSeq numberSeq; TransDate maturityDate; CustVendPDCRegister _custVendPDCRegister; //TODO: Mak sure to replcae RecId as per your system in next line of code.// you have to take recid of pdc select _custVendPDCRegister where _custVendPDCRegister.RecId == 5637146078; maturityDate = _custVendPDCRegister.MaturityDate; if(_custVendPDCRegister.pdcStatus!=PostDatedCheckStatus::Posted || _custVendPDCRegister.MaturityDate > systemDateGet()) { checkFailed("@SYS322445"); return; } journalName = BankParameters::find().GeneralJournalNamePDC; if(!journalName) { checkFailed("@SYS322456"); return; } ledgerJournalTrans = LedgerJournalTrans::findRecId(_custVendPDCRegister.LedgerJournalTrans, false); while select generalJournalAccountEntry join RecId from subledgerVoucherGeneralJournalEntry where subledgerVoucherGeneralJournalEntry.GeneralJournalEntry == generalJournalAccountEntry.GeneralJournalEntry && subledgerVoucherGeneralJournalEntry.Voucher == ledgerJournalTrans.Voucher && subledgerVoucherGeneralJournalEntry.AccountingDate == ledgerJournalTrans.TransDate && subledgerVoucherGeneralJournalEntry.VoucherDataAreaId == ledgerJournalTrans.DataAreaId && generalJournalAccountEntry.PostingType != LedgerPostingType::InterunitCredit && generalJournalAccountEntry.PostingType != LedgerPostingType::InterunitDebit join RecId,SubledgerVoucher,AccountingDate from generalJournalEntry where generalJournalEntry.RecId == subledgerVoucherGeneralJournalEntry.GeneralJournalEntry && generalJournalEntry.Ledger == Ledger::current() outer join * from ledgerEntryJournal where ledgerEntryJournal.JournalNumber == ledgerJournalTrans.JournalNum && ledgerEntryJournal.RecId == generalJournalEntry.LedgerEntryJournal outer join * from ledgerEntry where ledgerEntry.GeneralJournalAccountEntry == generalJournalAccountEntry.RecId && ledgerEntry.IsBridgingPosting == NoYes::Yes exists join ledgerTransFurtherPosting where ledgerTransFurtherPosting.RefRecId == ledgerEntry.RecId && ledgerTransFurtherPosting.AccountNum == ledgerJournalTrans.accountDisplay() { info(strFmt('Voucher: %1 - Date: %2 - Amount in Trans Currency: %3',generalJournalEntry.SubledgerVoucher,generalJournalEntry.AccountingDate,generalJournalAccountEntry.TransactionCurrencyAmount)); } }
*This post is locked for comments