Monday, 23 April 2018

Mark Customer/Vendor Invoice/Invoices for settlement using X++ in D365 or AX 2012

Below method can be used to mark the Invoice amounts for settlement in very easy way.

In your code to loop through ledgerjournaltrans(assuming you already created payment lines for respective Customer/vendor, if not, then create payment record either by X++ or direct entry through payment screens).

Just for reference, i am creating payment via x++ and marked the invoices for settlement.
I am posting full process (create & Post SO, Create Payment entry, Mark invoice for settlement, post payment entry)

form class Declaration.
public class FormRun extends ObjectRun
{   
    CustTable   custtable_g;
    CustInvoiceJour     bitcustInvoiceJour;
}

Below method can be used for SO creation and Payment entry.

public void TestReceipt()
{
    SalesTable salesTable;
    NumberSeq NumberSeq;
    SalesLine salesLine;
    SalesFormLetter formLetterObj;
    InventTable inventTable;

    Ledgerjournalname           ledgerjournalname;
    LedgerjournalTable          LedgerjournalTable;
    LedgerjournalTrans          LedgerjournalTrans;
    LedgerJournalCheckPost      ledgerJournalCheckPost;

    ;

    try
    {
        ttsBegin;
        //Create sales header
        NumberSeq = NumberSeq::newGetNumFromId(SalesParameters::numRefSalesId().NumberSequenceId);
        salesTable.SalesId = NumberSeq.num();
        salesTable.initValue();
        salesTable.CustAccount = custtable_g.AccountNum; //custtable_g is already initialized
        salesTable.initFromCustTable();
        salesTable.insert();

        //Create Sales lines
        salesLine.clear();
        salesLine.SalesId   = salesTable.SalesId;
        salesLine.ItemId    = ItemId.valueStr(); 
        inventTable = InventTable::find(ItemId.valueStr());
        salesLine.initFromInventTable(inventTable);
        salesLine.SalesQty  = Quantity.value();
        salesLine.createLine(NoYes::Yes, // Validate
        NoYes::Yes, // initFromSalesTable
        NoYes::Yes, // initFromInventTable
        NoYes::Yes, // calcInventQty
        NoYes::Yes, // searchMarkup
        NoYes::Yes); // searchPrice

        //confirmation
        //formLetterObj = SalesFormLetter::construct(DocumentStatus::Confirmation);
        //formLetterObj.update(salesTable,Selldate.dateValue());

        //Packing Slip
        formLetterObj = SalesFormLetter::construct(DocumentStatus::PackingSlip);
        formLetterObj.update(salesTable,Selldate.dateValue());

        //Post Invoice
        formLetterObj = SalesFormLetter::construct(DocumentStatus::Invoice);
        formLetterObj.update(salesTable,Selldate.dateValue());

        ttsCommit; 

        //
        ttsBegin;
        bitcustInvoiceJour = formLetterObj.parmJournalRecord();

        //Generate and post receipt

        select ledgerjournalname where ledgerjournalname.JournalName == "PayJ"; //use correct payment journal name

        //ttsBegin;
        LedgerjournalTable.JournalName = ledgerjournalname.JournalName;
        LedgerjournalTable.initFromLedgerJournalName();
        LedgerjournalTable.JournalNum  = JournalTableData::newTable(LedgerjournalTable).nextJournalId();      
        LedgerjournalTable.insert();

        numberSeq                               =   NumberSeq::newGetVoucherFromId((ledgerjournalname.NumberSequenceTable));
        LedgerjournalTrans.Voucher              =   numberSeq.voucher();
        LedgerjournalTrans.JournalNum           =   LedgerjournalTable.JournalNum;
        LedgerjournalTrans.AccountType          =   LedgerJournalACType::Cust;
        LedgerjournalTrans.Company              =   curext();
        LedgerjournalTrans.parmAccount(custtable_g.AccountNum,LedgerjournalTrans.AccountType);
        LedgerjournalTrans.initFromCustTable(custtable_g);
        LedgerjournalTrans.TransDate            = Selldate.dateValue();

        LedgerjournalTrans.ExchRate             =   Currency::exchRate(LedgerjournalTrans.CurrencyCode);

        LedgerjournalTrans.Txt                  =   strFmt('Payment for item sale for sales id : %1',salestable.SalesId);


        LedgerjournalTrans.DefaultDimension         =   custtable_g.DefaultDimension;
        LedgerjournalTrans.OffsetDefaultDimension   =   custtable_g.DefaultDimension;
        LedgerjournalTrans.TransactionType          =   LedgerTransType::Payment;
        LedgerjournalTrans.AmountCurCredit          =   bitcustInvoiceJour.InvoiceAmount;
        LedgerjournalTrans.PaymMode                 =   CustPaymMode.valueStr();
        LedgerjournalTrans.SettleVoucher            =   SettlementType::SelectedTransact;
        LedgerjournalTrans.modifiedField(fieldNum(LedgerjournalTrans,PaymMode));
        LedgerjournalTrans.insert();


        element.settlement(LedgerjournalTrans,LedgerjournalTrans.AmountCurCredit); //look for method

        //Post Journal
        ledgerJournalCheckPost = LedgerJournalCheckPost::newLedgerJournalTable(LedgerjournalTable,NoYes::Yes);
        ledgerJournalCheckPost.run();

        ttsCommit;
      
        info(strFmt("Journal %1 created for item sales Id %2 to Student id %3",LedgerjournalTable.JournalNum,salestable.SalesId,custtable_g.AccountNum));

    }
    catch
    {
        error("error in generating receipt for sale.");
    }


}

Below method can be used for settlement of required amount
void settlement(ledgerJournalTrans _ledgerJournalTrans,real _amounttoSettle)
{
    SpecTrans insertSpecTrans;
    ;
    insertSpecTrans.SpecCompany = _ledgerJournalTrans.Company;
    insertSpecTrans.SpecTableId = TableNum(ledgerJournalTrans);
    insertSpecTrans.SpecRecId   = _ledgerJournalTrans.RecId;
    insertSpecTrans.RefCompany  = _ledgerJournalTrans.Company;
    insertSpecTrans.RefTableId  = TableNum(custTransOPen);
    insertSpecTrans.RefRecId    = custTransOPen::findRefId(bitcustInvoiceJour.custTrans().RecId).RecId;
    insertSpecTrans.Balance01   = _amounttoSettle;
    insertSpecTrans.Code        = _ledgerJournalTrans.CurrencyCode;
    insertSpecTrans.Payment     = NoYes::No;
    insertSpecTrans.LineNum     = 1;
    insertSpecTrans.insert();

}

yes, I am done here. Thanks!!!

Monday, 12 February 2018

Free Text Invoice - Get InvoiceId before posting an Invoice with X++ in D365 or AX 2012

I came across a requirement where client asked us to display actual Invoice Id before actually posting it in the system(They needed it as per their business, they send Invoices to Customers before posting so if any amendment has to be made, it can be made directly to Order/Free text invoice and post it afterwards).

Luckily, not much change required for this.

Navigate to class CUSTPOSTINVOICE and add new method as below

// method to get/set Invoice and voucher
private container getNumAndVoucher(NumberSeq _numberSeq)
{
   container ret;
   if (custInvoiceTable.InvoiceID != '')
   {
       _numberSeq.parmNumberSequenceCode('');
       _numberSeq.parmNumberSequenceId(0);
       ret = [custInvoiceTable.InvoiceID, custInvoiceTable.Voucher];
   }
   else
   {
       ret = _numberSeq.numAndVoucher();
   }
   return ret;
}

Then, open RUNINTERNAL method of same class for editing and scroll down to the line where it says

numberSeq = this.allocateNumAndVoucher();

Then copy and comment the above line and move to next if/else condition

 if (custInvoiceTable.InvoiceId
                && RetailMCRChannelTable::findForCurrentUser().mcrEnableOrderCompletion)

and modify else part of this as shown below

  //changed, moved from line 164
       numberSeq = this.allocateNumAndVoucher();//paste commented line of code
       if (countryRegion_LTLV)
       {
            [invoiceId, voucher] = this.getNumAndVoucher_W(numberSeq);
            if (! CustInvoiceJour::checkDuplicateNum_W(invoiceId, '', custInvoiceTable.InvoiceDate))
            {
                throw error("@SYS25904");
            }
      }
      else
      {
           //new code
           [invoiceId, voucher] = this.getNumAndVoucher(numberSeq);
      }

After this, include a below piece of code(modify as per the calling place) at required place to save invoiced and voucher for posting.

// to update invoice and voucher
    Select forUpdate custInvoiceTable1 where custInvoiceTable1.RecId == custInvoiceTable.RecId;
    custPostInvoice = new CustPostInvoice(custInvoiceTable1);
    numberSeq = custPostInvoice.allocateNumAndVoucher();

    [invoice,voucher] = numberSeq.numAndVoucher();
    custInvoiceTable1.InvoiceId = invoice;
    custInvoiceTable1.Voucher = voucher;
    custInvoiceTable1.update();


Now you'll have actual invoice id in Invoice field of Free text invoice table(CustInvoiceTable) for further reporting and posting.

Share your thoughts and comments if any.

Friday, 19 January 2018

CRUD with Odata and Data entities in Dynamics 365 for operations - CREATE, UPDATE and DELETE Operations

You can use POSTMAN or FIDDLER for any of the testing. I personally like POSTMAN.

I assume that you have already gone through pages on READ operations. If not, please go through all that for better understanding of upcoming discussion on CREATE,UPDATE and DELETE operations in Dynamics 365 for operations(D365O).

Reading through data entities was fun, isn't it. But as a developer you must be willing to do more than just reading the data using Data entities.

Microsoft has done commendable work in whole data entity framework in D365O, it now can be used as point of entry for other operations such as create, update and delete. This a big relief considering AIF is practically a history for D365O.

Although there are other ways also to deal with data inside D365O, I find using Data entities the easiest.

I have created a custom table named 'BeaconService' for my testing purpose.

1. CREATE operations:

In Odata terminology, D365 CREATE operation can be referred as POST operation. Yes, you are right, that means you have to setup a method type to POST.

URL would look like this

https://*******devaos.cloudax.dynamics.com/data/Beaconservice

Set the Authorization code prefixed by 'bearer'

Then Set the body, keeping mandatory fields like BeaconID(in my case) and dataareaId as shown below.

{
      "EndTime": 0,
      "BeaconNum": "DA:AF:A9:FD:Y6:T4",
      "OfferId": "ST100006",
      "Description": "25% flat discount for your amazing audio experience",
      "EffectiveDate": "2015-12-25T12:00:00Z",
      "StartTime": 0,
      "dataAreaId": "usrt",
      "Category": "Headphones",
      "Offer": "headphones on discounted rate",
      "Brand": "Sony"
}



Now execute it (click on Send button if using POSTMAN).
This will give same output on the postman and error if any. Now you can go to the database or D365 screen to see the data.

2. UPDATE Operations


In Odata terminology, D365 UPDATE operation can be referred as PATCH operation.
i.e., you have to setup method type as PATCH.

URL would look like this
https://*******devaos.cloudax.dynamics.com/data/Beaconservice(BeaconNum='DA:AF:A9:FD:Y6:T4',dataAreaId='usrt')

Difference in the URL as you can see is, you'll have to include your primary key along with datareaid.

Now, Set the Authorization code prefixed by 'bearer'

Then Set the body as below, here you do not have to include primary keys

{
    "Category": "Earphones"
}


Now execute it (click on Send button if using POSTMAN).
This will leave the output screen blank if succeeded and error if any. Go ahead and verify the data.

3. DELETE Operations


In Odata terminology, D365 UPDATE operation can be referred as DELETE operation.
i.e., you have to setup method type as DELETE.

URL would look like this
https://*******devaos.cloudax.dynamics.com/data/Beaconservice(BeaconNum='DA:AF:A9:FD:Y6:T4',dataAreaId='usrt')

Here also, you'll have to include your primary key along with datareaid.

Now, Set the Authorization code prefixed by 'bearer'

Leave the body blank here. 


Then execute it (click on Send button if using POSTMAN).
This will leave the output screen blank if succeeded and error if any.
--------------------------------------------------------------------------------------

I am done here.
Thanks for your time!!!
Shoot the message if anything is missing or needed to be modified.

Saturday, 13 January 2018

DIXF for Multiple AOS running on Single Server for AX 2012

Reg DIXF, AX installer does not allows to configure DIXF if you are going ahead for 2nd AOS on same server.

Luckily, I've got some workaround.

Just open the server installation folder for older AOS where DIXF is working, and copy 3 files as mentioned below
Server path : C:\Program Files\Microsoft Dynamics AX\60\Server\MicrosoftDynamicsAX\bin

Files to be copied:
1. Microsoft.Dynamics.AX.Framework.Tools.DMF.ServiceProxy.dll
2. DMFClientConfig
3. DMFConfig

And the paste these files as it is in Bin folder of other server.

That's it! You are done, open AX and do rest of the DIXF setup and play around.