Wednesday 29 August 2012

Sales orders – commissions in Ax 2009


Setup Commission posting
Microsoft Dynamics® AX2009 calculates sales commissions at the sales line level upon invoicing a sales order.
The Commission posting form is used to specify a general ledger account for a commission posting to be debited only during an invoice update, and also as an offset account that is credited. Click Navigation Pane node: Accounts receivable -> Setup -> Commission -> Commission posting. Select ‘Commission’  to setup debit account and ‘Commission offset’ to setup credit account.
Customer groups for commissions
When calculating the commission of a sales line Microsoft Dynamics AX 2009 searches for the related sales representative, item, and customer combination set up in the Commission calculation form. Groups of sales representatives, items, and customers can also be set up in Commission relationships in the Commission calculation form.
Use Commission customer groups (CCG) to integrate groupings of customers into the calculation of commission transactions for sales orders. Notice that CCGs are not necessarily connected to customer groups. Click Navigation Pane node: Accounts receivable -> Setup -> Commission -> Customer groups for commission.
For a commission to be calculated with regard to a specific CCG, the CCG must be associated with a sales order. A CCG can be set up on:
- Customer – Specify the CCG on the Sales order tab in the Customer commission group field. This defaults to the sales order when creating a sales order with this customer.
- Sales order – Specify the CCG on the Setup tab in the Customer group field. Notice that a setting in the sales order overrides the setting from the Customers form.
To link CCG with the customer, click Navigation Pane node: Accounts receivable -> Customer Details. Switch to the Sales order tab on the Customers form. Change Commission Customer Group.
To link sales order with the CCG, click Navigation Pane node: Accounts receivable -> Sales Order Details.  Switch to the Setup tab and select ‘Sales group’, select CCG. If you already have selected the group for the customer, it will be populated automatically.
Commission item groups
Use Commission item groups (CIG) to integrate combinations of items into the commission calculation. Notice that although CIGs are completely independent of ordinary item groups Inventory management > Setup > Item groups it might be desirable to group items into CIGs under similar premises. You can’t change CIG in the order line.
To check existing Item groups, open Inventory management -> Setup -> Item groups.
To create new CIG Accounts receivable -> Setup -> Commission -> Item groups.
To link items with CIG, click Navigation Pane node: Inventory management -> Item details, switch to the General tab on the Item form.
Commission sales group
The purpose of grouping employees is broader than the purpose of grouping customers and items for commission agreements. Within the Commission sales group (CSG) is the specification of which employees may receive a commission when a customer associated with the relevant sales group buys certain items.
Click Navigation Pane node: Accounts receivable -> Setup -> Commission -> Sales groups.
Open Sales rep. form. to setup sales representatives (they need to be in the employee table).
You can allocate percentages of both less than 100% and more than 100%. If the amount is more than 100%, a warning message appears. However, the system correctly calculates the amount if this warning is ignored and an over allocation is required.
You can also associate employees with a sales group by clicking Human Resources > Employee Details. Select an employee and click Commission, select the sales group to attach the employee to.
Click Navigation Pane node: Human Resources -> Employee Details. Click the Commission -> Sales groups menu button.
The same effect as creating a Table relation for a sales rep can be created by only attaching one salesperson to the CSG (Contact).
To integrate CSGs into the calculation of sales order commissions, specify a CSG at one of the following levels:
- Customer – Specify the CSG on the Sales order tab in the Sales group field. This defaults to the sales order when creating a sales order with this customer.
- Sales order – Specify the CSG in the Sales group field. This overrides the setting specified on the customer.
- Sales order line – Specify the CSG in the Sales group field. This overrides the customer and sales order settings.
Commission calculation
Click Navigation Pane node: Accounts receivable -> Setup -> Commission -> Commission calculation.
Go to the Setup tab, specify the following options:
  • The period the commission calculation is valid.
  • When commission calculation occurs: Before line discount, After line discount or After the total discount.
  • Whether the commission calculation is based on the contribution Margin or on Revenue earned for the sales order
  • In the Find next field, specify whether Microsoft Dynamics AX 2009 is to continue searching for more commission agreements for a salesperson. If this option is not selected, any agreements on the Group or All levels, for example, will be suppressed.  Microsoft Dynamics AX 2009 searches to find whether there are employees associated with a Sales group who are to receive a commission.  It will accumulate commissions for all possible level. The search for a commission calculation agreement moves from specific relations to general – all relations:   Table level, Item and Customer combination;  Group level combination of Item and Customer combination; - All – which is the general level.
  • If during the search a specific table relation exists, this overrides a commission calculation setup at the group level and in the same manner as a group setting overrides a general All setting.
View commission transactions
To see created commission transactions, click Navigation Pane node: Accounts receivable -> Customer Details. Click the Inquiries -> Invoice menu button. Commission -> Commission transactions menu button.
To see all of commission transactions posted for employee, click Navigation Pane node: Human Resources -> Employee Details. Click the Commission -> Commission transactions menu button.
Source: Dynamics Ax training materials.

Tuesday 28 August 2012

Create Sales Order in Ax 2012 using X++

Hi,
In this post we will learn the below using X++ :

 1) How to Create Sales Order
 2) How to Create Sales Order Line
 3) How to Post Sales Order


Code :
public static void SalesOrderDemo(Args _s)
{
// Create the Sales Order

SalesTable salesTable;
NumberSeq NumberSeq;
SalesId sid;
SalesLine sl;
SalesFormLetter fl;
;
NumberSeq =NumberSeq::newGetNum(SalesParameters::numRefSalesId() , true);
sid=NumberSeq.num();
salesTable.SalesId = sid;
salesTable.initValue();
salesTable.CustAccount = "1101";
salesTable.initFromCustTable();
salesTable.insert();

//Create the Sales Line with the created Sales Order
sl.SalesId=sid;
sl.ItemId="1101";
sl.CreateLine(NoYes::Yes,NoYes::Yes,NoYes::Yes,NoYes::Yes,NoYes::Yes,NoYes::Yes);

info("Sales Order Created with Line");

//How to Post the Sales Order Invoice


fl=SalesFormLetter::construct(DocumentStatus::Invoice);
fl.update(SalesTable::find(sid));
info("Sales Order Posted");

}

Friday 10 August 2012

Dynamics AX 2012 Financial Dimensions

Hi Friends,

Today we will be exploring the new changes related to Financial dimensions in AX 2012.

Earlier versions AX 2009, 4.0 :
  • In older version System use to limit the creation of dimensions up to 10. 
  • By default 3 dimensions were available in the system namely Department, CostCenter and Purpose.
    Technically these dimensions were controlled by Enum SysDimension and an array EDT Dimension.
  • So If we have 3 enum elements in SysDimension, then its corresponding array elements are stored in    dimension EDT and can be referred as Dimension[0], Dimension[1].....
  • If you would like to store these dimension values against customized table, then simply we were adding the EDT Dimension to that Table. At Form level simply drag and drop field on Group then system use to  show all the array elements as string control.
New version AX 2012 (6.0) : 

  • In AX 60, their is no limit to dimension creations. One can create n number of dimensions as per    organization requirements.
  • Technically their is huge change in framework, the way these dimensions used to be created and stored.
  • Firstly EDT Dimension (array) is deprecated and replaced with DimensionDefault (Int64 RecId).    
  • Financial Dimensions master table Dimension is replaced with DimensionAttribute and DimensionValueDetails.
  • Now if one wish to store these dimension on your customized table then instead of EDT Dimension one should add DimensionDefault EDT.       
  • At Form level make use of DimensionDefaultingController class to show the available dimensions.
How to access Financial Dimensions values in AX 2012: 
     
 In earlier version, accessing dimension values was very simple like
                 CustTable.Dimension[0]      ==  Department  value    
                 CustTable.Dimension[1]      ==  CostCenter  value
                 CustTable.Dimension[2]      ==  Purpose       value

 In AX 60, since they have replaced Dimension EDT with RecId DimensionDefault we have to make use of Dimension helper classes to access values.So in CustTable you will find defaultDimension field which stores reference recId for DimensionAttributeSet.       

 I am referring Customer 1101 in CEU Company, Contoso Demo Data for illustration purpose. Note the dimension values for this customer as shown in below snap.




  
Refer the code to access the values.

static void DEV_Dimension(Args _args)
{
    CustTable                         custTable = CustTable::find("1101");
    DimensionAttributeValueSetStorage dimStorage;
    Counter i;
      
    dimStorage = DimensionAttributeValueSetStorage::find(custTable.DefaultDimension);
  
    for (i=1 ; i<= dimStorage.elements() ; i++)
    {
        info(strFmt("%1 = %2", DimensionAttribute::find(dimStorage.getAttributeByIndex(i)).Name,        
                               dimStorage.getDisplayValueByIndex(i))); 
    }
}    
   
When you run the job, will see as below


I know that now you might have understood the importance of theses helper classes, they are making our lives easy :)














Wednesday 8 August 2012

Cheque Printing functionality in Ax2009

Cheque Printing in Ax2009 is very simple.Already there are classes and reports available in Ax2009.
Just the matter is change the develop the new report(with the help of existing Cheque reports) and code in classes.
First:
Create a new base enum in ChequeFormType(ex:HDFC)
Scond:
List of classes for Cheque:
1.BankPrintTestCheque(for testing the layout of the cheque)
2.BankChequePrint(is the main class for 'Bank Cheque printing')
3.CustVendCheque(calles when vendor payment and customer payemnt->generate payment)
add the code whereever ChequeFormType enum is used :)-
Third:
dublicate the existing report 'Cheque_DK' and rename.
change the design as per the HDFC Bank layout.

Test ur cheque layout process:-
1. select a perticular bank a/c in bank details form under Bank Module.
2. Click setup button->cheque layout.
3. select cheque number as fixed.Select check form as 'HDFC' then click Print test button.
Note: before testing the print layout for hdfc bank need to develop a new report.

The same report is called when u print the report from the vendor payment journal :)-
good luch

Tuesday 7 August 2012

How batch processing works under the hood AX2009


In this article I am going to explain how batch processing in AX2009 works, I don't mean
how to set up a batch group or any of that kind of thing that you find in the manual,
what I mean is what each AOS is doing in the background to decide how and when to pick
up batches and process and complete them. Understanding this background can help in
advanced batch troubleshooting or development scenarios.
In AX2009 batch processing changed. Now we have AOSes which can run batch processes
directly, if you want to see what's happening with a batch process, it can be more difficult than
 in AX3 or AX4 as there is no client sitting there running to look at.
What happens now is that each AOS has a dedicated thread which checks for batches, basically
 all this does is calls Classes\BatchRun.ServerGetTask() once every 60 seconds (timing is not 
configurable) and if there is any work for that AOS to do then the AOS will pick up a task from here.

I'll give an example of an end-to-end batch process to show what happens where and when:
- A report is sent to batch by a user, it goes into the batch queue in BATCHJOB (header) and 
BATCH (the batch tasks).
- Once every 60 seconds each AOS that has been configured for batch processing 
(in administration->setup->server configuration) will call the X++ method - 
Classes\BatchRun.serverGetTask()
- In serverGetTask() the logic is exposed in X++ so we can all see what happens, this is the
 main place that we decide what to pick up for batch processing. Basically it checks if there 
is any tasks in the BATCH table waiting for this AOS - based on the batch groups that this 
AOS is configured to process, and based on the time that the records in BATCH are due to
 be processed (i.e. something processes at 21:00 each day then it won't get picked up until 
21:00 despite the fact that the AOS polls every 60 seconds). There are a few stages to this method:
 1. First we check if there is a task (a task is a record in BATCH table) ready for us, the query for 
this is like this:
select firstonly pessimisticlock RecId, CreatedBy, ExecutedBy, StartDateTime, Status,
        SessionIdx,SessionLoginDateTime, Company, ServerId, Info
    from batch
    where batch.Status == BatchStatus::Ready
    && batch.RunType == BatchRunType::Server
    && (Session::isServer() || batch.CreatedBy == user)
    join Language from userInfo
        where userInfo.Id == batch.CreatedBy
        && userInfo.Enable == true
    exists join batchServerGroup
        where batchServerGroup.ServerId == serverId
            && batch.GroupId == batchServerGroup.GroupId;
 2. If a task is returned in step 1 then there's nothing more to do and we start processing that task.
 If no task is returned then we look to see if any batch jobs need to be started, the query for this is like this:
 update_recordset batchJob setting
                Status = BatchStatus::Executing,
                StartDateTime = thisDate
            where batchJob.Status == BatchStatus::Waiting
                &&  batchJob.OrigStartDateTime   <= thisDate
            exists join batch
                where batch.BatchJobId == batchJob.RecId
            exists join batchServerGroup
                where batch.GroupId == batchServerGroup.GroupId
                && batchServerGroup.ServerId == serverId;

 
3. After step 2 we will run Classes\batchRun.serverProcessDependencies(). In here something 
interesting happens - we see that we use this table "BatchGlobal", this is used as a focal point,
 because we might have several AOSes running batch processing in the same environment,
 and so for some operations we look to this table to see if another AOS has already done
 something, to decide whether the current AOS needs to do it as well or not. For dependencies 
we just make sure that another AOS is not doing this in the same second. So if we continue here, 
the queries we run to set more tasks (again tasks are just records in the BATCH table) ready for 
processing are below - you can see in the queries how we update the status on the BATCH
 table records, checking that we only do it for records which are ready and do not have any 
constraints that are not completed yet:
   
//There are no more available tasks and the user is asking for any task.
Search for more tasks with
    //dependencies
    update_recordset batch setting Status = BatchStatus::Ready
    where batch.Status == BatchStatus::Waiting
        && batch.ConstraintType == BatchConstraintType::Or
    exists join batchJob
        where batchJob.Status == BatchStatus::Executing
            && batch.BatchJobId == batchJob.RecId
    exists join constraintsOr
        where constraintsOr.BatchId == batch.RecId
    exists join batchDependsOr
      where
     (
        ((batchDependsOr.Status == BatchStatus::Finished
            && (constraintsOr.ExpectedStatus == BatchDependencyStatus::Finished
                || constraintsOr.ExpectedStatus == BatchDependencyStatus::FinishedOrError))
        || (batchDependsOr.Status == BatchStatus::Error
            && (constraintsOr.ExpectedStatus == BatchDependencyStatus::Error
                || constraintsOr.ExpectedStatus == BatchDependencyStatus::FinishedOrError)))
        && constraintsOr.DependsOnBatchId == batchDependsOr.RecId
     );

    update_recordset batch setting Status = BatchStatus::Ready
    where batch.Status == BatchStatus::Waiting
        && batch.ConstraintType == BatchConstraintType::And
    exists join batchJob
        where batchJob.Status == BatchStatus::Executing
            && batch.BatchJobId == batchJob.RecId
    notexists join constraintsAnd exists join batchDependsAnd
    where
     (
        constraintsAnd.DependsOnBatchId == batchDependsAnd.RecId
        && constraintsAnd.BatchId == batch.RecId
        && ((batchDependsAnd.Status != BatchStatus::Finished && batchDependsAnd.Status != BatchStatus::Error)
            || (constraintsAnd.ExpectedStatus == BatchDependencyStatus::Finished
                && batchDependsAnd.Status == BatchStatus::Error)
                || (constraintsAnd.ExpectedStatus == BatchDependencyStatus::Error
                && batchDependsAnd.Status == BatchStatus::Finished))
     );
4. When this serverProcessDependencies() is complete in step 3 we call again to serverGetOneTask() (same as in step 1), if there were some more tasks set to "ready" in step 3 then we might pick up a task to work on here. Of course if not tasks were "ready" in step 3 then we won't find a task and we'll just do nothing.
- So our report which we sent to batch, if in the steps numbered 1-4 above, we found this record was ready to process, and we picked it up, what happens next inside the AOS kernel is that we start a worker session, which can be thought of a bit like a client session, just without a client, it will have it's own session ID and you'll see the ID recorded against the record in the Batch table. From this point it calls BatchRun.runJobStatic() and actually runs the batch process - this is just normal X++ running the process here. When this runJobStatic() completes we call BatchRun.ServerFinishTask(), which just sets the status of the record in BATCH to either "finished" or "error" (if it failed for some reason).
- Now our batch task is finished - the record in the BATCH table. But the header for this batch, the Tables\BatchJob record is not set to finished yet. For this part there is another background process running every 60 seconds on each AOS which just calls into BatchRun.serverProcessFinishedJobs(). Now we can see in this X++ method what it does - we use this BatchGlobal table again, to make sure that between all AOSes we only check for finished jobs a maximum of once every 60 seconds, if it has been 60 seconds then we will run a whole load of queries (too many to copy here but you can check there to see them) to create the batch history (various tables), set the BatchJob record to finished and delete the completed tasks and constraints.
There are a couple of other background things that happen in the AOS kernel for batch processing:
1. Every 5 minutes it will call to BatchRun.serverCleanUpDeadTasks() - again we use the BatchGlobal table, so that we'll only run this once every 5 minutes between all AOSes. This just sets tasks back to "ready" if the session ID for the worker session (I mentioned this earlier - we create this worker session when we start processing a task) is no longer a valid session - basically if a task fails with an X++ exception, or something like that, then the worker session will end, and if you have configured this batch task to allow some retries, then it's this method which will reset the task for it to have a retry.
2. Every 5 minutes each AOS will check the server settings, to see if it's supposed to process the same batch groups - or if it's not supposed to be a batch server any more, all those settings.

Creating a batch class in Dynamics AX 2009



For creating a batch class in Dynamics AX we can start from the Tutorial_RunbaseBatch class,
 but here is the explanation for understanding all its methods:

In the classDeclaration, we declare any variable that contains user's selection or preferences
 (for example, the customer account for launch a customer report). Also, we declare other
 variables like dialog fields, auxiliar variables, etc. But, the most important feature here, is the #localmacro.CurrentList declaration. This macro helps you to define which variables you want
 to save for running the class in batch mode (when not user interaction is available).

class Tutorial_RunbaseBatch extends RunBaseBatch
{
      //Packed variables
      TansDate           transDate;
      CustAccount      custAccount;

      // Dialog fields
      DialogField        dlgCustAccount;
      DialogField        dlgTransDate;

      // Current version
      // We can different versions for different purposes
      #define.CurrentVersion(1)
      #define.Version1(1)
      #localmacro.CurrentList        // Our current variable list for save user's selected values
        transDate,
        custAccount
   #endmacro
}

The macro #define.CurrentVersion(1) and #define.Version1(1) are useful for scaling our class
 when we need to change/upgrade it preserving compatibility.

Now two methods: pack and unpack. These methods are used for store and retrieve user's 
preferences and perform the batch operation when none user are available (in batch mode).
 Here is the point where we use the macro defined previously:



public container pack()
{
    return [#CurrentVersion,#CurrentList];
}

public boolean unpack(container packedClass)
{
    Version version = RunBase::getVersion(packedClass);
;
    switch (version)
    {
        case #CurrentVersion:
            [version,#CurrentList] = packedClass;
            break;
        //case #otherVersion:
            //[version,#CurrentList, #otherList] = packedClass;
            //break;
        default:
            return false;
    }

    return true;
}


Bacause we need to ask the user about preferences for run this object (report,
 process, query, etc.), we must implement some methods:

dialog: for build the custom dialog for prompting user's preferences.
dialogPostRun: after dialog was created, you can perform other UI tasks here.
getFromDialog: used for retrive user's preferences (dialog values).
validate: here, you can perform a validation for any data entered by the user.

public Object dialog()
{
    DialogRunbase       dialog = super();
    ;
 
    // Creation of our custom dialog fields/controls to ask user for preferences
    dlgTransDate = dialog.addFieldValue(typeid(TransDate),transDate);
    dlgCustAccount = dialog.addFieldValue(typeid(CustAccount),custAccount);

    return dialog;
}

public void dialogPostRun(DialogRunbase dialog)
{
;
    super(dialog);
}

public boolean getFromDialog()
{
    ;

    // Retrieving user's preferences
    transDate   = dlgTransDate.value();
    custAccount = dlgCustAccount.value();

    return super();
}

public boolean validate()
{
    // We can perform some validations here
    if (false)
        return checkFailed("");

    return true;
}


Finally, the main method constructs the object from this batch class, and after
 prompting user and validation was successful, the run method is called to perform 
some task (in batch mode if it was selected):

server static Tutorial_RunbaseBatch construct()
{
    return new Tutorial_RunbaseBatch();
}

public void run()
{
    try
    {
        // Obviously, this code is silly, why show something to nobody?
        // remember, this code is running in batch without user interaction
        info(strfmt("Parameters: %1 for %2", transDate, custAccount));
    }
    catch(Exception::Deadlock)
    {
        retry;
    }
    catch(Exception::UpdateConflict)
    {
        throw Exception::UpdateConflict;
    }
    catch
    {
        throw Exception::Error;
    }
}

static void main(Args args)
{
    Tutorial_RunbaseBatch    tutorial_RunBase;
    ;

    // Instanciating this batch class
    tutorial_RunBase = Tutorial_RunbaseBatch::construct();

    // Prompting user and run if all is ok
    if (tutorial_RunBase.prompt())
        tutorial_RunBase.run();
}