The sample shows how to implement custom receipt fields and custom receipt types.
Sample overview:

The sample shows how to implement custom receipt fields and custom receipt types.

Song Nghia - Technical Consultant

Setup steps:

Custom Receipt Fields
1. Create labels.
    - In AX search for "Language text".
    - Go to that page and create three labels: "TipAmount", "ItemNumber" and "TenderId".
    - Specify a different Text ID to each of them and set Language ID as "en-us".
2. Create custom receipt fields.
    - In AX search for "Custom fields".
    - Go to that page and create three custom fields: "TipAmount", "ItemNumber" and "TenderId".
    - Set the Type as Receipt.
    - Assign corresponding Caption text ID you created in previous step to them.
3. Modify the receipt format.
    - In AX search for "receipt format".
    - Open the designer for receipt type "Receipt".
    - Now you should be able to see those three new fields you created in previous steps.
    - Drag and drop them to the canvas and save the receipt format.
    - Put the TipAmount in the header or footer section.
    - Put the ItemNumber in the body section.
    - Put the TenderId on the same line of TenderName or TenderAmount.
4. Run download job 1090 (Registers).
5. Open project at RetailSDK\SampleExtensions\CommerceRuntime\Extensions.ReceiptsSample\CommerceRuntime.Extensions.ReceiptsSample.csproj and compile it.
6. Use inetmgr to find the location of the local Retail Server
7. Copy the Contoso.Commerce.Runtime.ReceiptsSample.dll to the bin folder of RetailServer. This will only work on this development machine. For a real customization dll needs to be added to the deployment package, "register" it in Customizations.settings file (see the Retail Sdk Handbook.pdf for details)
8. Register the CRT change in commerceruntime.ext.config (Note: Please DO NOT edit commerceruntime.config file. This file is not meant for any customizations.):
    <add source="type" value="Contoso.Commerce.Runtime.ReceiptsSample.GetCustomReceiptFieldsService, Contoso.Commerce.Runtime.ReceiptsSample" />
9. Now make a normal transaction in POS and check out the transaction. Now you should be able to see those values printed on the receipt.
Custom Receipt Type
1. Create a new receipt format.
    - In AX search for "Receipt format"
    - Set the Receipt type as CustomReceiptType6.
    - Set the Print behavior as Always print.
    - Give other fields appropriate values.
    - Save the changes.
    - Open the designer for this format.
    - Put TransactionNumber in the header. Put ItemId in the body. Put some words(Text) in the footer part.
    - Save the changes.
2. Add this receipt format to receipt profile.
    - In AX search for "receipt profile".
    - Go to that page, select the receipt profile that is currently being used.
    - Click Add button, and select CustomReceiptType6. Select the receipt format you previously created. Save the changes.
4. Run download job 1090 (Registers).
5. Open project at RetailSDK\SampleExtensions\CommerceRuntime\Extensions.ReceiptsSample\CommerceRuntime.Extensions.ReceiptsSample.csproj and compile it.
6. Use inetmgr to find the location of the local Retail Server
7. Copy the Contoso.Commerce.Runtime.ReceiptsSample.dll to the bin folder of RetailServer. This will only work on this development machine. For a real customization dll needs to be added to the deployment package, "register" it in Customizations.settings file (see the Retail Sdk Handbook.pdf for details)
8. Register the CRT change in commerceruntime.ext.config (Note: Please DO NOT edit commerceruntime.config file. This file is not meant for any customizations.):
    <add source="type" value="Contoso.Commerce.Runtime.ReceiptsSample.GetCustomReceiptsRequestHandler, Contoso.Commerce.Runtime.ReceiptsSample" />
9. Customize POS to print the new receipt type after the standard receipts:
    - Subscribe to PostEndTransaction trigger.
    - At the trigger, call getReceiptsForPrintAsync  setting the parameter ReceiptTypeValue as 106(CustomReceiptType6).
10.In POS make a transaction.
    - Add item 0001, 0002, 81134 and 81135 into cart.
    - Check out the transaction.
General Journals Database & Business

Song Nghia - Technical Consultant

1. Business perspective

General journal is the most common way to insert financial transaction into AX. So it is important that you understand the basics of using it.
(General Ledger > Journals > General journal > [Button] Lines)
General journal
A journal in AX is like a temporary book where you write things down. When you are done, you validate the data and then you post it. Once it is posted, it is like you have sealed it with ink and can not change it. Well, not legally anyway.

If you haven't posted a journal in AX 2012 for a long time, I suggest that you play with it a bit more before going technical.
###Dimensions As we talked about last week, there are Accounts (ledger dimension) and Financial dimensions (default dimension). Financial dimensions are only used only if the account type is not Ledger. Let's look in an example:
In this case you can see that the account is a bank and we complemented with the Business Unit 004 "IT consulting practice". When we post this journal it will become the following voucher:You will notice that the bank account GBSI OPER is translated to main account 110150 and the financial transaction is merged together into the Ledger account field. In the second and third line, which are of account type Ledger, are just posted as they were.
This is how the dimensions are used in general journal and voucher transactions. All other account types (customer, vendor, asset, project) will follow the same pattern as the bank example.
If you wonder how the magic happened for translating bank account to a main account the setting can be found in the form Bank accounts.

Management Reporter

There are many ways to see posted transactions. The easiest way is through Voucher transactions (General Ledger > Inquiries > Voucher transactions) as we seen in the previous example. This is the most detailed way of viewing individual transactions.
New in AX 2012 is the Management Reporter, which is the recommended way to view aggregated voucher data. The strength with MR is that it is highly customizable. Due to the complex data model in finance it requires more effort to customize reports in SSRS. MR on the other hand has a lot of predefined column and row definitions, which make it easy even for non-developers to create their own specialized reports. Management Reporter is a great tool and deserves its own article, which will come soon.

2. Data Model

We will separate the data model into two entities: general journal and voucher transactions. The first is before posting and the latter is after posting.
###General journal The general journal data model is very simple and consists basically of two three tables: LedgerJournalName, LedgerJournalTable and LedgerJournalTrans. There are of course a lot of reference data like dimensions, exchange rates, tax, parties and so on, but we will leave those in the perifery while focusing on the core.
Journal names
General journal
Journal voucher

General ledger > Setup > Journals > Journal names

General ledger > Journals > General journals

General ledger > Journals > General journals > [button] lines
LedgerJournalName keeps the settings for the journal type.
Typical settings are voucher number sequence, journal type, journal control, posting restictions, approval and default financial dimensions. Click on the image above to see more.
LedgerJournalTable holds the information for each journal.
Each record has to have a journal name. Many of the settings e.g. financial dimension will inherit the values from the chosen LedgerJournalName.
LedgerJournalTrans is where the
journal vouchers gets stored.
Each journal can hold unlimited of
 journal vouchers (unless it is limit through Lines limit on the journal).
LegerJournalTable. JournalName == LedgerJournalName. JournalName
LedgerJournalTrans. JournalNum == LedgerJournalTable. JournalNum

Voucher transactions

After the journal gets posted the voucher transaction creates. Here we will explain three central tables.
Keeps relation between LedgerJournalTable and GeneralJournalEntry
Holds information for each voucher.
Holds information for each transaction with account
LedgerEntryJournal. JournalNumber == LedgerJournalTable. JournalNum && LedgerEntryJournal. LedgerJournalTableDataArea == LedgerJournalTable.DataAreaId
GeneralJournalEntry. LedgerEntryJournal == LedgerEntryJournal.RecId
GeneralJournalAccountEntry. GeneralJournalEntry == GeneralJournalEntry.RecId
Class that generates records

3. Create journals with AIF
The service for creating journals is called LedgerGeneralJournalService. As all other service classes we can use it directly through X++. Here is an example for that.
static void createLedgerJournal(Args _args)
    LedgerGeneralJournalService             service;
    LedgerGeneralJournal                    ledgerGeneralJournal;
    LedgerGeneralJournal_LedgerJournalTable ledgerJournalTable;
    LedgerGeneralJournal_LedgerJournalTrans ledgerJournalTrans;

    AfStronglyTypedDataContainerList    list;
    AifMultiTypeAccount                 ledgerDimension;
    AifDimensionAttributeValueSet       defaultDimension;
    AfStronglyTypedDataContainerList    values;
    AifDimensionAttributeValue          value;
    AifEntityKeyList                    keyList;
    ledgerGeneralJournal = new LedgerGeneralJournal();

    ledgerJournalTable = ledgerGeneralJournal.createLedgerJournalTable().addNew();
    list = ledgerJournalTable.createLedgerJournalTrans();

    //LedgerJournalTrans 1
    ledgerJournalTrans = new LedgerGeneralJournal_LedgerJournalTrans();

    ledgerDimension = ledgerJournalTrans.createLedgerDimension();
    values = ledgerDimension.createValues();
    value = values.addNew();
    value = values.addNew();

    //LedgerJournalTrans 2
    ledgerJournalTrans = new LedgerGeneralJournal_LedgerJournalTrans();

    ledgerDimension = ledgerJournalTrans.createLedgerDimension();
    ledgerDimension.parmAccount('GBSI OPER');
    ledgerDimension.parmDisplayValue('GBSI OPER');
    values = ledgerDimension.createValues();
    value = values.addNew();
    value.parmValue('GBSI OPER');

    defaultDimension = ledgerJournalTrans.createDefaultDimension();
    values = defaultDimension.createValues();
    value = values.addNew();

    service = LedgerGeneralJournalService::construct();
    keyList = service.create(ledgerGeneralJournal);
    info(strFmt('Created RecId: %1', keyList.getEntityKey(1).parmRecId()));
The C# code for AIF Web Services looks very similar to the X++ code so I'll skip that. There are plenty of articles with sample code if you Bing it. In short: activate Web Service in AX, connect the web reference in Visual Studio and then let IntelliSense do the magic. What we don't see that often is the AIF file integration, so I'll add the XML here. Notice the XML namespace for the dimensions are from the shared types namespace.
<?xml version="1.0" encoding="utf-8"?>
<Envelope xmlns="">
            <LedgerGeneralJournal xmlns="">
                <LedgerJournalTable class="entity">
                    <LedgerJournalTrans class="entity">
                            <DisplayValue xmlns="">605150-004-025--</DisplayValue>
                            <Account xmlns="">605150</Account>
                            <Values xmlns="">
                    <LedgerJournalTrans class="entity">
                            <Values xmlns="">
                            <DisplayValue xmlns="">GBSI OPER</DisplayValue>
                            <Account xmlns="">GBSI OPER</Account>

4. Migrate journals with DIXF

AIF is great because it can handle multiple formats from Web Services, MSMQ, files etc. And you can get synchronous error messages. But sometimes you want a one-time import of a large batch of data and be able to validate the data before importing to the real table. In that case, DIXF is great. You can also expand the code behind the entity so that you process the data in a certain way, for instance transform Microsoft Dynamics AX 2009 dimensions to AX 2012 dimensions.
In this example we will use the predefined entity Opening balance to generate journal lines. The entity Opening balances is supposed to be used for exporting/importing balances. But it works fine for this example. The steps are:
  1. Create a new Processing group (Data import export framework > Common > Processing framework).
  2. Click the Entities button, create a new entity and select Opening balance. In this sample we use a CSV as the Source data format.
  3. Create a CSV file with the content from below and save it as OpeningBalanceImport.csv. Change the data that seems fit. In this case I used the GBSI company in the AX 2012 R3 Demo VPC.
    GBP,00000339,1.0000000000000000,TEST0001,0.00,100.00,Ledger,605150-004-025,,2015-04-01 00:00:00
    GBP,00000339,2.0000000000000000,TEST0001,100.00,0.00,Bank,GBSI OPER,,2015-04-01 00:00:00
  4. Set the Sample file path as your new file.
  5. Click Generate source mapping and then Validate.
  6. If everything went well then go back to the Processing group form and click Get staging data and then Copy data to target.
  7. If everything went well then you should be able to see open your journal lines in the journal.

5. Posting journals

It is possible to post journals by code using the class LedgerJournalCheckPost. One of the use-cases for posting journals by code could be to validate all journals that comes in from integration or migration. Another use-case is that you can create an AIF Web Service, so that the integrating subsystem can post the journal after creating it.
public static void main(Args _args)
    LedgerJournalCheckPost ledgerJournalCheckPost; 
    LedgerJournalTable ledgerJournalTable;
    ledgerJournalTable = ledgerJournalTable::find('00000336');
    ledgerJournalCheckPost = LedgerJournalCheckPost::newLedgerJournalTable(
        ledgerJournalTable, NoYes::Yes, NoYes::Yes);;
This will result in an info message: Number of vouchers posted to the journal: 1.
As you can see in the example LedgerJournalCheckPost::newLedgerJournalTable(...) takes three arguments.
  • LedgerJournalTable _ledgerJournalTable - This is the LedgerJournalTable that you want to post.
  • NoYes _post - If you only want to validate without posting then set _post as No. It is possible to have several vouchers in the same journal. Sometimes one of the vouchers can have errors.
  • NoYes _transferErrors - If you set _transferErrors as Yes, then the journal will get posted and the erroneous vouchers will get transferred to a new journal. If you set _transferErrors as No and there are errors, then the posting will get cancelled and the journal still opened.
The example code is a very simple version, in reality you should add more control to the flow. For instance try-catch-statement and also add some follow up flow like:
if (!ledgerJournalCheckPost.numOfErrorsInList() && ledgerJournalCheckPost.numOfVouchersBooked())
    info('Success'); //Your success flow
    info('Failed'); //Your failed flow
Phiên bản MVP của Microsoft
Phiên bản MVP của Microsoft

Song Nghia - Technical Consultant

Lịch sử
Khởi đầu từ các framework Smart Client Software Factory and Web Client Software Factory dựa trên kiến trúc MVP được các nhóm nghiên cứu về pattern của Microsoft tạo ra từ năm 2006, Microsoft sau đó bắt đầu đưa MVP vào các tài liệu hướng dẫn và ví dụ về lập trình giao diện trong .NET framework. Mô tả dưới đây dựa trên phiên bản MVP được mô tả trong tạp chí MSDN vào năm 2006 (tham khảo 2) và có thể xem là kiến trúc cơ bản cho các framework trên. Ngoài ra, còn có một số framework MVP khác cũng được phát triển trên nền tảng .NET như MVC# hay NMVP.
Xin lưu ý là phiên bản cũ của Web Client Software Factory ban đầu được tổ chức dựa trên Passive View (cấu trúc bên dưới) nhưng với phiên bản hiện tại thì cấu trúc dựa trên Supervising Controller cũng được hỗ trợ.
Cấu trúc

Các thành phần

Model chứa dữ liệu và các tính toán xử lý logic để giải quyết vấn đề mà phần mềm hướng tới (business logic).
View là thành phần đảm nhận trình bày từ những dữ liệu của Model và là tổng hợp của các form, control được sử dụng.
Presenter là thành phần đảm nhận các xử lý về trình bày cũng như tương tác đến dữ liệu bên dưới và có thể tương tác để thay đổi View trong quá trình xử lý.
Phối hợp các thành phần
Mẫu kiến trúc MVP của Microsoft tương tự như Passive View nhưng nó bổ sung thêm ràng buộc là các Presenter chỉ có thể truy cập đến View thông qua các interface IView. Điều này giúp Presenter không phụ thuộc đến cài đặt cụ thể của View và có thể dễ dàng test Presenter một cách độc lập. Điều này đạt được do sử dụng IView, chúng ta có thể dùng kĩ thuật “Mock” để thay thế các xử lý cụ thể của View khi test Presenter. Ngoài ra, mối quan hệ giữa Presenter và Model là quan hệ một chiều (chiều còn lại là gián tiếp). Hệ quả của các liên kết là chúng ta có một mô hình đa lớp (multi-layer) như cấu trúc ở trên theo ý nghĩa các thành phần ở một layer chỉ phụ thuộc và sử dụng các thành phần ở layer ngay bên dưới nó.
Theo mẫu Microsoft MVP, khi một View gắn liền với một interface IView. Khi View được tạo ra, nó tạo ra một đối tượng private Presenter và gắn nó vào đối tượng này thông qua IView. Khi một sự kiện xảy ra, View bắt lấy và sau đó kích hoạt một phương thức của Presenter mà sau đó, nó có thể tương tác với Model. Một số sự kiện như bắt đầu hiển thị và đóng của View cũng được gửi tới để Presenter kích hoạt một số phương thức tương ứng, điều này giúp cho một số công việc như khởi tạo và giải phóng dữ liệu cho View được dễ dàng hơn.
Model theo Microsoft MVP cũng thường được tổ chức bổ sung một lớp Service nằm bên trên để tương tác với Presenter, qua đó giảm bớt sự phụ thuộc đến các xử lý data nằm sâu bên dưới. Ngoài ra, để điều khiển việc liên lạc giữa các View hay các Presenter, một thành phần thường được bổ sung gọi là Application Controller. Trong một ứng dụng có thể có nhiều Application Controller để quản lý các nhóm MVP. Ngoài ra, thay vì sử dụng Presenter để tương tác với Model, một cách là giao tất cả các thao tác này cho Application Controller đảm trách.
Một ví dụ minh họa đơn giản
Để minh họa mẫu Microsoft MVP, chúng ta sẽ cùng khảo sát một ví dụ đơn giản: tạo một form hiển thị danh sách sinh viên cùng chức năng thêm mới trên danh sách sinh viên đó. Với mục tiêu là minh họa cách tổ chức và liên kết giữa các thành phần là chủ yếu, chúng ta không đi sâu vào cài đặt cụ thể (bạn có thể tham khảo source code đi kèm bài viết, source code được tổ chức ở dạng project của VS 2008).
Ứng dụng được tổ chức thành 5 project: View, Presenter, DataService, Model và DataTransferObject. Trong đó, View là application còn các project khác là library. Các project phụ thuộc lẫn nhau theo thứ tự project phía trước sử dụng trực tiếp project phía sau, ngoại trừ DataTransferObject chức các loại dữ liệu được dùng chung cho 3 project View, Preseneter và DataService. Model là project chứa các tương tác trực tiếp đến dữ liệu và không phụ thuộc vào bất cứ thành phần nào khác.

Giải thích các project:
  • Model: chứa dữ liệu giả lập (StudentData.xml), schema (StudentDataSet.xsd) và lớp tiện ích được xây dựng bên trên (DataAccess.cs).
  • DataTransferObject: chứa đối tượng dữ liệu được dùng chung cho các project ở mức cao hơn (Student.cs).
  • DataService: được xây dựng bên trên Model, chuyển đổi dữ liệu từ Model sang DataTransferObject và xây dựng các tiện ích bên trên đối tượng transfer này (Service.cs).
  • Presenter: chứa interface của View dùng cho Presenter truy cập (IViewForm) và lớp Presenter (StudentPresenter).
  • View: chứa form view (ViewForm.cs) và Program.cs.
Trong bài viết khác, chúng ta sẽ tìm hiểu phương pháp TDD (Test-driven development) và ứng dụng nó để phát triển ứng dụng theo mẫu Microsoft MVP trong môi trường .NET.
Tài liệu tham khảo:
  1. Martin Fowler, GUI Architectures
  2. Jean-Paul Boodhoo , Design Patterns: Model-View-Presenter
  3. Buschmann F., Meunier R., Rohnert H. & Sommerlad P. & Stal M. (1996). Pattern-Oriented Software Architecture: A System of Patterns.
  4. Martin Fowler , Patterns of Enterprise Application Architecture.
  7. Derek Greer, Interactive application architecture patterns
  8. Views Testability Guidance
  9. Dolphin MVP paper

Product, Product Master, Release Product, Product type
Product, Product Master, Release Product, Product type

Product : Item không sử dụng product dimension => không có variant

Product master: Item sử dụng product dimension.

- Bao gồm 1 mã chính, và có nhiều biến thể gọi là variant

Released product: Item tồn tại ở danh mục chung, sau đó đươạ release cho công ty nào.

vd: Item001 -> release công ty A, B => công ty A, B sẽ thấy.
      Item002 -> release công ty C=> chỉ C sẽ thấy, A,B không thấy.

=> Để mã giống nhau, công ty nào sử dụng trước release trước, sau dùng sau.

Product type

Product type gồm 2 loại:

Item: Có quản lý tồn kho

Service: Không quản lý tồn kho
Find role of employee

Song Nghia - Technical Consultant

Find role of employee

class SCA_FindRoleOfUser



    /// Runs the class with the specified arguments.


    /// The specified arguments.

    public static void main(Args _args)


        SecurityRole        role;

        SecurityUserRole    userRole;

        UserInfo            userInfo;

        // Check the AOT macro for the path names


        SysModelElement         sysModelElement;

        treeNode treeNode;


        // start syncdatabase
















        // --end

        while select firstonly  role

        exists join userRole

        where role.RecId     == userRole.SecurityRole

              && userRole.User == "VNBMH"


            info(strFmt("%1  -  %2", role.Name, curUserId()));



Get Privileges from AOT Tree - D365 - AX 2012

Get Privileges from AOT Tree X++ / D365

Song Nghia - Technical Consultant

class SCA_GetPrivileges


    /// Runs the class with the specified arguments.
    /// The specified arguments.

    public static void main(Args _args)



        str find        = "PurchReqTable";

        TreeNode root   = TreeNode::findNode(#SecPrivilegesPath);

        TreeNode            current;

        TreeNodeTraverser   trav = new TreeNodeTraverser(root,false);

        current =;

        while (current)

            if ((current.AOTgetProperty('ObjectType') == "Form") && (current.AOTgetProperty('Object') == find))

                info(strFmt("Found menuitem %1",current.AOTname()));

            current =;