DAX 2012 Remove reservations and markings from a sales order

Some friends of me have asked how to remove reservations and markings on a sales order. Going through line by line can be time consuming and they wanted to know how this could be solved by a single “click”.

 

static void Tutorial_UnreserveSalesTable(Args _args)
{
    InventTrans             inventTrans;
    InventTransOrigin       inventTransOrigin;
    SalesLine               salesLine;
    InventMovement          inventMovement;
    InventUpd_Reservation   inventUpd_Reservation ;
    SalesId                 SalesId = "001260";//<--- Send/get sales id here

    // Remove reservations and markings on a reserved salesorder
    while select inventTrans
        where inventTrans.StatusReceipt                == StatusReceipt::None
           && (inventTrans.StatusIssue                 == StatusIssue::ReservPhysical 
           ||  inventTrans.StatusIssue                 == StatusIssue::ReservOrdered)
        exists join inventTransOrigin 
            where   inventTransOrigin.RecId            == inventTrans.InventTransOrigin
        exists join salesLine 
            where   salesLine.InventTransId            == inventTransOrigin.InventTransId                    
                &&  SalesLine.SalesId                  == SalesId    
    {
            if (inventTrans.MarkingRefInventTransOrigin)
            {
                InventTransOrigin::deleteMarking(inventTrans.MarkingRefInventTransOrigin, inventTrans.InventTransOrigin, -inventTrans.Qty, true);
                InventTransOrigin::deleteMarking(inventTrans.InventTransOrigin, inventTrans.MarkingRefInventTransOrigin, inventTrans.Qty, true);
            }

            if (inventTrans.StatusIssue == StatusIssue::ReservPhysical || inventTrans.StatusIssue == StatusIssue::ReservOrdered)
            {                
                Inventmovement = inventTrans.inventmovement(true); 
                inventUpd_Reservation = InventUpd_Reservation::newInventDim(inventmovement,inventTrans.inventDim(), -1 * inventTrans.Qty, false); 
                inventUpd_Reservation.updatenow(); 
            }
    }
}

The next step would be to create a class, so that it can be performed in CIL, and some code that refreshes the sales order for the end-user.

AX2012 – How to get Retail Store specific CUE’s

In Dynamics AX, the role center may contain a lot, but some times it is wanted to show more specific and isolated information. In this blog-post I would like to show you how to create a cue that only shows number of sales and amount related to a specific retail store. This blogpost is meant to show the pattern we can use, and is not only restricted to retail. We can use this technic almost everywhere in Dynamics AX.

Here we see a CUE where I show the number of sales and the amount for a specific store. But the nice thing here, it that the values changes depending on what store the user is assigned to.

Do be able to do this we need a dynamic query, that changes the store, depending on the current user, and we need to have a place, where a user is assigned to a store. This is already available in Dynamics AX, and is a setting on the User profiles

So here, I’m assigning a user to a role, and to a store. The next thing I need to have is a way to sum up all the retail sales transactions, and for this I use the form Retail sales transactions.

I then create an advanced filter by using the CTRL-F3, and save as a CUE.

You see here that in the criteria for store number, I write (EGCurrentRetailStoreID()). This is a very nice trick in Dynamics AX. The ability to create dynamic queries. I have blogged about it before here.

The code needed to use dynamic query criteria is located in SysQueryRangeUtil is the following:

This code will return the store ID for the current user. The last step is to select the CUE in the shared role center.

Then you have a CUE, that shows the sales in your local retail store.

 

Happy DAX’ing

New DAX 2012 R3 CU8 Global Retail codeplex solution available

Hi friends.

I’m happy to announce the first release of a new free codeplex solution; Global Retail.

The solution covers:

  1. Global Product price templates – The ability to assign a price template to a product, that will quickly enable retail prices available in different countries and currencies.
  2. Global Product pricing – The ability to set recommended retail prices directly on products. Global prices are posted in price journals to price agreements in all legal entities.
  3. Global Product barcodes – Assign and maintain product barcodes, and it will automatically synchronize to all legal entities.
  4. Simplified Product translations and simplified local pricing screen. Multi dimensional retail hierarchies for filtering, search and availability. Full price log and the use of case management for price change processes.
  5. Simplified file import and export of products, retail hierarchies, barcodes, prices, translations, availability, counting journals. (Excel, csv, xml)

Take a look here > https://ax2012globalretail.codeplex.com/ (more documentation will come later)

PS! This is the real-deal !

Giving away your silver for a golden future


(Picture is borrowed from Robin Dickinson)

In the AX-Ecosystem we see that blogging, sharing and networking is strong and open. Colleagues and knowledge friends find each other across company borders and culture. Quite a lot business is actually created through this community. And those that is not here will struggle to get into position. Some people ask me: “Why are you doing this ?” This quick answer is: “Because I like to, and because it is fun”. But there are more to it than that. Here is the “secret two line” business model of sharing.

  • Give away some ideas, components/code, wisdom and experience – for free – and hope that the dynamics ecosystem (the crowd) will use it.
  • Then do our best to make sure that some of them want to buy more of our ideas, our know-how and our hard-earned wisdom and experience.

Some companies talk about the value of protecting their IP-rights, and they have IP-rights on everything. Down to the little small code samples. But an IP-based business model have some fundamental issues:

  • Revenue is often based on licenses and yearly support fee’s.
  • Requires substantial costs on sales, presales, support and R&D efforts to meet standards and expectations.
  • Keeping it on the “pricelist” have a cost.
  • Customers expect components to be compliant with new Microsoft versions and releases.
  • Bug and Issues are expected to be fixed free of charge.
  • Risks quickly becomes expensive

The assumption is that the revenue must be higher than the costs of maintain and keeping the components on the price list.

However, what we see is that Investments stops. No innovation. Very difficult to find for the customers. Knowledge is lost, and IP based components are often not profitable in long term.

 

So, what kind of things are we talking about ?

  • Small ideas, that would never be a product.
  • Potential reusable components.
  • Customer financed customizations.
  • Basically things we cannot build an ordinary business model on.

One alternative is to use Codeplex as a business model.

  • The Dynamics ecosystem is small. Very easy to get high visibility.
  • Sharing is a very positive message. Can strengthen the your brand.
  • Can increase service revenue. New customers seek knowledge from the sources.
  • Triggers customers “Pull” behavior instead of “Push”.
  • Reach new potential employees that take a pride on sharing their knowledge and ideas.

A few days ago, I wanted to test this approach, so I blogged about a very small code piece I gave away, and wanted to measure the effectiveness, and here is the effect after 2 days.


How much would it cost in traditional marketing and brand building to achieve this reach ? Surely more then the small hour I used to publish it. I can also assure you that by giving away some of your work though sites like codeplex do build brand value and do generate a profitable and golden future.

 

(*From the Coca Cola dude)

Dynamics AX 2012 R3- To exist or not, that is the question!

The advanced filter and query in Dynamics AX is a very powerful tool. Here you can search on most fields, and also add relations to the query.

You can open this advanced query screen from almost any AX screen, and the short-cut is “CTRL-F3″.

But there is one area that the advanced query screen is not handling. That is “Not-exists-join”. Let’s say I want a list of all the purchase orders that DON’T have purchase lines. The standard AX will not help you here. Often a developer must come inn and create a smart query or script to find this. With the introduction of attributes and retail categories, I see the need for this “not-Exists-join” feature to be able to create advanced queries.

I don’t accept this, because I want to empower the end-user to perform such queries. So here is my gift to the Dynamics AX ecosystem; NotExistsJoin directly on the advanced query screen:

Then I can look at the filtered purchase list page, and I only see purchase orders that DON’T have any purchase lines J

This will work allover dynamics AX, even on cue’s and on other query based functionality.

So what’s the trick? A few changes to the following two classes:

Let’s take the changes “one-by-one”, or you can download the code here : https://dynamicsax2012r3notexists.codeplex.com/

Then here is the last change to get a icon on not-exists join:

Have fun DAX’ing ;-)

Dynamics AX 2012 R3: Global recommended retail price

Maintaining retail prices in a multi company and franchise scenario can be a challenge in Dynamics AX 2012 R3. Let say we have a retailer with stores in many countries. We often also see owner structures that makes us model the retail chain with many companies.

I’m currently in an implementation to an international retailer present in 22 countries, and it keeps growing every day. To model this we need to create one Dynamics AX company per country and per partner.

The ability to maintain and control prices in such a scenario is a challenge, because prices is a property of a released item. (PriceDiscTable)

So we decided to create very small customization so that we could keep track of prices and item barcodes on the product instead of the released product level.

By doing this, the product managers have one screen where they can create and maintain product prices. The prices can be specified per currency, country and company/partner. But this is not just a simple table, but a two-way frontend for populating the standard AX price tables.

We therefore have a shared table:


The code for publishing prices into standard AX, using the price journal posting looks like this:


void publishProductRetailPrices()
{
    #Admin
    #Zebra
    DataArea                            dataArea;
    PriceDiscTable                      priceDiscTable;
    PriceDiscAdmTable                   priceDiscAdmTable;
    PriceDiscAdmTrans                   priceDiscAdmTrans;
    PriceDiscAdmCheckPost               priceDiscAdmCheckPost;
    InventDimId                         inventDimId;
    Ledger                              ledger;
    CompanyInfo                         legalEntity;
    LogisticsAddressCountryRegionId     countryRegionId;
    #define.ImportPriceDiscJournalName("Prices")
    PriceDiscAdmName                    priceDiscAdmName;

    ttsbegin;
    while select Id from  dataArea where dataArea.Id != #DATCompany
    {
        changecompany(dataArea.Id)
        {
            legalEntity             = CompanyInfo::find();
            ledger                  = Ledger::findByLegalEntity(legalEntity.RecId);
            countryRegionId         = CompanyInfo::find().postalAddress().CountryRegionId;
            this.createOrUpdatePriceGroup();

            if (this.RefCompanyId       == dataArea.Id ||
                this.CountryRegionId    == countryRegionId ||
               (this.RefCompanyId == "" && this.CountryRegionId == "" && this.CurrencyCode == ledger.AccountingCurrency))
           {
                if (InventTable::exist(this.ProductNumber))
                {
                    priceDiscAdmName = this.findOrCreatePriceDiscAdmName(#ImportPriceDiscJournalName,"@SYS342509");

                    priceDiscAdmTable.clear();
                    priceDiscAdmTable.JournalName       = priceDiscAdmName.JournalName;
                    priceDiscAdmTable.Name              = priceDiscAdmName.Name;
                    priceDiscAdmTable.insert();

                    priceDiscAdmTrans.clear();
                    priceDiscAdmTrans.initValue();
                    inventDimId = InventDim::findOrCreateBlank().InventDimId;

                    while select firstonly priceDiscTable
                        where   priceDiscTable.Relation         == PriceType::PriceSales
                            &&  priceDiscTable.ItemCode         == TableGroupAll::Table
                            &&  priceDiscTable.ItemRelation     == this.ProductNumber
                            &&  priceDiscTable.AccountCode      == TableGroupAll::GroupId
                            &&  priceDiscTable.AccountRelation  == #RRP
                            &&  priceDiscTable.Currency         == this.CurrencyCode
                            &&  priceDiscTable.InventDimId      == inventDimId
                            &&  priceDiscTable.PriceUnit        == this.PriceUnit
                    {
                        priceDiscAdmTrans.initFromPriceDiscTable(priceDiscTable);
                    }
                    priceDiscAdmTrans.Relation         = PriceType::PriceSales;
                    priceDiscAdmTrans.ItemCode         = TableGroupAll::Table;
                    priceDiscAdmTrans.ItemRelation     = this.ProductNumber;
                    priceDiscAdmTrans.AccountCode      = TableGroupAll::GroupId;
                    priceDiscAdmTrans.AccountRelation  = #RRP;
                    priceDiscAdmTrans.Currency         = this.CurrencyCode;
                    priceDiscAdmTrans.InventDimId      = inventDimId;
                    priceDiscAdmTrans.PriceUnit        = this.PriceUnit;
                    priceDiscAdmTrans.JournalNum       = priceDiscAdmTable.JournalNum;
                    priceDiscAdmTrans.QuantityAmountFrom  = 0;
                    priceDiscAdmTrans.Amount           = this.Amount;
                    priceDiscAdmTrans.UnitId           = InventTable::find(this.ProductNumber).salesUnitId();
                    priceDiscAdmTrans.SearchAgain      = NoYes::Yes;
                    priceDiscAdmTrans.insert();
                    if(priceDiscAdmTable.RecId)
                    {
                        priceDiscAdmCheckPost = new PriceDiscAdmCheckPost(false);
                        priceDiscAdmCheckPost.initJournalNum(priceDiscAdmTable.JournalNum);
                        priceDiscAdmCheckPost.run();
                        infolog.clear();
                    }
                }
            }
        }
    }
    ttscommit;
}