Customer invoices and payments settlement using x++ code in dynamics 365 Finance and Operations

Customer invoices and payments settlement using x++ code in dynamics 365 Finance and Operations

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!

  1. Create a contract class 'AA_CustVendOpenTransSettleContract'

[
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.

要查看或添加评论,请登录

Syed Amir Ali的更多文章

社区洞察

其他会员也浏览了