Customer invoices and payments settlement using x++ code in dynamics 365 Finance and Operations
Syed Amir Ali
Microsoft Certified Dynamics 365 Finance & Operations Specialist | MCT | Techno-Functional Supply Chain Consultant
Hello, Dynamics 365 F&O Developer! In this article, I’ll guide you on how to automatically reconcile unsettled customer invoices with payments using X++ code.
This technical blog post will provide a solution to this common problem. To do this, we’ll create a batch job with two parameters: a dropdown for the customer ID and a checkbox for selecting all customers. If you wish to run the batch for all customers, simply check the ‘All Customers’ box. Alternatively, if you want to run the operation for a specific customer, enter the customer account ID in the dropdown. Let’s dive in!
[
DataContractAttribute
, SysOperationContractProcessingAttribute(classStr(AA_CustVendOpenTransSettleUIBuilder))
]
public class AA_CustVendOpenTransSettleContract
{
List customerIdList;
NoYes allCustomers;
[
DataMemberAttribute(identifierStr(SalesId)),
SysOperationLabelAttribute(literalstr("@SYS7149")),
SysOperationHelpTextAttribute(literalstr("@SYS7149")),
AifCollectionTypeAttribute('return', Types::String)
]
public List parmCustomerId(List _customerIdList = customerIdList)
{
customerIdList = _customerIdList;
return customerIdList;
}
[
DataMemberAttribute(identifierStr(NoYes)),
SysOperationLabelAttribute(literalstr('All customers'))
]
public NoYes parmAllCustomers(NoYes _allCustomers = allCustomers)
{
allCustomers = _allCustomers;
return allCustomers;
}
}
2. Create a UIBuilder class
public class AA_CustVendOpenTransSettleUIBuilder extends SysOperationAutomaticUIBuilder
{
#define.lookupAlways(2)
DialogField customerIdDf,allCustomerDf;
CustAccount customerId;
AA_CustVendOpenTransSettleContract contract;
public void Build()
{
contract = this.dataContractObject();
customerIdDf = this.addDialogField(methodStr(AA_CustVendOpenTransSettleContract, parmCustomerId), contract);
allCustomerDf = this.addDialogField(methodStr(AA_CustVendOpenTransSettleContract,parmAllCustomers ), contract);
}
public void postBuild()
{
customerIdDf = this.bindInfo().getDialogField(contract,methodStr(AA_CustVendOpenTransSettleContract,parmCustomerId ));
//Register overrides for form control events
customerIdDf.registerOverrideMethod(
methodstr(FormStringControl, lookup),
methodstr(AA_CustVendOpenTransSettleUIBuilder, customerIDLookup),
this);
customerIdDf.registerOverrideMethod(
methodStr(FormStringControl, leave),
methodStr(AA_CustVendOpenTransSettleUIBuilder, customerIDLeave),
this);
}
/// <summary>
/// Customer Id Lookup
/// </summary>
/// <param name = "_control">_control</param>
private void customerIDLookup(FormStringControl _control)
{
container con;
Query query = new Query();
QueryBuildDataSource qbds,qbds1;
qbds=query.addDataSource(tableNum(CustTable));
qbds1 = qbds.addDataSource(tableNum(DirPartyTable));
qbds1.addLink(fieldNum(CustTable, Party),fieldNum(DirPartyTable, RecId));
qbds.fields().dynamic(NoYes::Yes);
qbds1.fields().dynamic(NoYes::Yes);
qbds.addSelectionField(fieldNum(CustTable, AccountNum));
qbds1.addSelectionField(fieldNum(DirPartyTable, Name));
SysLookupMultiSelectGrid::lookup(query,_control,_control,_control,con);
}
/// <summary>
/// Customer Id leave
/// </summary>
/// <param name = "_control">_control</param>
/// <returns>true false</returns>
public boolean customerIDLeave(FormStringControl _control)
{
if (_control.valueStr() != '')
{
customerId = _control.valueStr();
}
return true;
}
public void postRun()
{
//super;
}
}
3. Service class
public class AA_CustVendOpenTransSettleService extends SysOperationServiceBase
{
[SysEntryPointAttribute(false)]
public void processOperation(AA_CustVendOpenTransSettleContract _contract)
{
List customerList = new List(Types::Class);
NoYes allCustomer;
customerList = _contract.parmCustomerId();
allCustomer = _contract.parmAllCustomers();
if(customerList.empty() && allCustomer == NoYes::No)
{
throw error("Atleast one customer must be selected or All customer checbox should be Yes to proceed");
}
else if(!customerList.empty() && allCustomer == NoYes::No)
{
this.settleCustomerInvoices(customerList);
}
else if(customerList.empty() && allCustomer == NoYes::Yes)
{
this.settleAllCustomerInvoices();
}
info("@SYS9265");
}
public void settleCustomerInvoices(List _customerList)
{
// This method is the entry point for the batch class
CustTable custTable;
CustTrans invCustTrans, payCustTrans;
SpecTransManager manager;
CustVendTransData custVendTransData;
Voucher myInvVoucher, myPayVoucher;
int x=1;
int Counter = 0;
CustAccount customerAccount;
ListEnumerator listEnum =new ListEnumerator();
listEnum = _customerList.getEnumerator();
while(listEnum.moveNext())
{
customerAccount = '';
customerAccount = listEnum.current();
custTable.clear();
select firstonly AccountNum from custTable
where custTable.AccountNum == customerAccount;
if(custTable.RecId)
{
info(strFmt('Customer # %1',custTable.AccountNum));
myInvVoucher='';
myPayVoucher='';
Counter = 0;
while ( x> 0)
{
// Find the oldest invoice that has not been settled yet
// for this customer you can customize the query find the invoice that you want to settle
select firstonly invCustTrans
order by DueDate asc
where invCustTrans.AccountNum == custTable.AccountNum &&
invCustTrans.AmountCur>0 &&
invCustTrans.TransDate <= DateTimeUtil::date(DateTimeUtil::getSystemDateTime()) &&
!invCustTrans.closed;
// Find the oldest payment that has not been settled yet
// for this customer
select firstonly payCustTrans
order by DueDate asc
where payCustTrans.AccountNum == custTable.AccountNum &&
payCustTrans.AmountCur<0 &&
payCustTrans.TransDate <=DateTimeUtil::date(DateTimeUtil::getSystemDateTime()) &&
!payCustTrans.closed;
if (invCustTrans.Voucher=='' || payCustTrans.Voucher=='')
break;
if (myInvVoucher==invCustTrans.Voucher && myPayVoucher==payCustTrans.Voucher)
{
if(Counter >= 400)
{
Counter = 0;
info(strFmt('Duplicate Vouchers));
break;
}
else
Counter++;
}
myInvVoucher='';
myPayVoucher='';
myInvVoucher=invCustTrans.Voucher;
myPayVoucher=payCustTrans.Voucher;
//info(strFmt('inv-voucher %1 pay-voucher %2',invCustTrans.Voucher,payCustTrans.Voucher));
ttsbegin;
// Create an object of the CustVendTransData class
// with the invoice transaction as parameter and mark
// it for settlement
custVendTransData = CustVendTransData::construct(invCustTrans);
custVendTransData.markForSettlement(custTable);
// Create an object of the CustVendTransData class
// with the payment transaction as parameter and mark
// it for settlement
custVendTransData = CustVendTransData::construct(payCustTrans);
custVendTransData.markForSettlement(custTable);
ttscommit;
// Settle all transactions marked for settlement for this
// customer
if(CustTrans::settleTransact(custTable, null, true,SettleDatePrinc::DaysDate, systemdateget()))
info('Transactions settled');
}
}
}
info("Batch job completed!");
}
public void settleAllCustomerInvoices()
{
// This method is the entry point for the batch class
CustTable custTable;
CustTrans invCustTrans, payCustTrans;
SpecTransManager manager;
CustVendTransData custVendTransData;
Voucher myInvVoucher, myPayVoucher;
int x=1;
int Counter = 0;
CustAccount customerAccount;
while select custTable
where custTable.CustGroup != '50'
{
if(custTable.RecId)
{
info(strFmt('Customer # %1',custTable.AccountNum));
myInvVoucher='';
myPayVoucher='';
Counter = 0;
while ( x> 0)
{
// Find the oldest invoice that has not been settled yet
// for this customer you can customize the query find the invoice that you want to settle
select firstonly invCustTrans
order by DueDate asc
where invCustTrans.AccountNum == custTable.AccountNum &&
invCustTrans.AmountCur>0 &&
invCustTrans.TransDate <= DateTimeUtil::date(DateTimeUtil::getSystemDateTime()) &&
!invCustTrans.closed;
// Find the oldest payment that has not been settled yet
// for this customer
select firstonly payCustTrans
order by DueDate asc
where payCustTrans.AccountNum == custTable.AccountNum &&
payCustTrans.AmountCur<0 &&
payCustTrans.TransDate <=DateTimeUtil::date(DateTimeUtil::getSystemDateTime()) &&
!payCustTrans.closed;
if (invCustTrans.Voucher=='' || payCustTrans.Voucher=='')
break;
if (myInvVoucher==invCustTrans.Voucher && myPayVoucher==payCustTrans.Voucher)
{
if(Counter >= 400)
{
Counter = 0;
info(strFmt('Duplicate Vouchers'));
break;
}
else
Counter++;
}
myInvVoucher='';
myPayVoucher='';
myInvVoucher=invCustTrans.Voucher;
myPayVoucher=payCustTrans.Voucher;
//info(strFmt('inv-voucher %1 pay-voucher %2',invCustTrans.Voucher,payCustTrans.Voucher));
ttsbegin;
// Create an object of the CustVendTransData class
// with the invoice transaction as parameter and mark
// it for settlement
custVendTransData = CustVendTransData::construct(invCustTrans);
custVendTransData.markForSettlement(custTable);
// Create an object of the CustVendTransData class
// with the payment transaction as parameter and mark
// it for settlement
custVendTransData = CustVendTransData::construct(payCustTrans);
custVendTransData.markForSettlement(custTable);
ttscommit;
// Settle all transactions marked for settlement for this
// customer
if(CustTrans::settleTransact(custTable, null, true,SettleDatePrinc::DaysDate, systemdateget()))
info('Transactions settled');
}
}
}
info("Batch job completed!");
}
}
4. Controller class
public class AA_CustVendOpenTransSettleController extends SysOperationServiceController
{
protected void new()
{
// call service class and service class method
super(classStr(AA_CustVendOpenTransSettleService), methodStr(AA_CustVendOpenTransSettleService, processOperation), SysOperationExecutionMode::Synchronous);
}
public static AA_CustVendOpenTransSettleController construct(SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)
{
AA_CustVendOpenTransSettleController controller;
controller = new AA_CustVendOpenTransSettleController();
controller.parmExecutionMode(_executionMode);
return controller;
}
public static void main(Args _args)
{
AA_CustVendOpenTransSettleController controller;
controller = AA_CustVendOpenTransSettleController::construct();
controller.parmArgs(_args);
controller.startOperation();
}
public ClassDescription defaultCaption()
{
return "Settle the unsettled customer invoices batch";
}
}
5. Add a action menu Item and set the Label and Object type with Object class name.
领英推荐
6. Add the action menu item on the menu.
7. Build the project.
Output Screen:
This is the end of this blog.
Happy Learning,
Syed Amir Ali.
Very helpful!