web
You’re offline. This is a read only version of the page.
close
Skip to main content

Announcements

News and Announcements icon
Community site session details

Community site session details

Session Id :
Dynamics 365 Community / Blogs / Hardik’s Dynamics Dojo / Stop Fighting Inventory Tra...

Stop Fighting Inventory Transactions: Use CustomInventTransList Instead

HardikPatel523 Profile Picture HardikPatel523 234

When Standard Transaction Selection Is Not What Your Business Wants

Ever been in this situation?

You create a sales order for 1,000 pieces.
Everything looks good.
Life is peaceful.
Coffee is still hot. ☕
Then you check the inventory transactions and suddenly...
You don't have one inventory transaction.
You have five.
Each for 200 quantity.
Each with a different batch.
And now the business says:
"We don't want the system to post the first batch."
"We want to post THIS specific batch."


And that's exactly where the fun begins.
Because standard Dynamics 365 Finance & Operations inventory posting has its own ideas about which transaction should be posted first.

Why Does This Problem Exist?

Let's take a common setup:
  • Item is batch controlled
  • Batch number group has Per Qty = 200
  • Physical Update = No
  • Sales order quantity = 1,000
When the order is created:
  • Transaction 1 → 200 Qty
  • Transaction 2 → 200 Qty
  • Transaction 3 → 200 Qty
  • Transaction 4 → 200 Qty
  • Transaction 5 → 200 Qty    
All with different batches.
Now imagine:    
  • Sales Posting
  • Purchase Posting
  • Production Posting
  • Transfer Posting
  • Any inventory update process
If you want to post only one specific batch, standard logic may not choose the transaction you want.
Instead, it follows its own sorting logic using things like:
  • StatusIssue
  • InventDimId
  • Quantity
As a result, the transaction created first is often selected first.
The business thinks in batches. Standard posting thinks in sorting rules.

Understanding the Concept

Most developers solve this problem by heavily customizing inventory posting logic.
But Microsoft already provides a framework for this.
And surprisingly, many developers never use it.
Meet:

CustomInventTransList

Think of it as a VIP guest list for inventory posting.
Instead of telling the system:
"Go find transactions yourself."
You tell it:
"Here is the exact list of transactions I want you to use."

And the system happily follows your instructions.
No hunting.
No guessing.
No unnecessary customizations.

The Architecture Behind the Magic

The overall flow looks like this:

Step 1: User Selects Transactions

You may have:
  • A custom form
  • Temporary table
  • Batch selection screen
  • Warehouse allocation screen
User selects the transactions to post.

Step 2: Build CustomInventTransList

Store selected transactions inside a context object.

Step 3: Pass List Into Inventory Update Framework

Before posting starts:
transSelectListContext.addCustomInventTransToList(inventTransLoc, inventTransLoc.Qty);

Step 4: Initialize Standard Framework

Inside inventory posting:
this.initializeCustomInventTransList(transSelectList.getInventTransSelectList());

Step 5: Standard Logic Uses Your List

Instead of querying the database, standard code consumes your transaction list.
Simple.
Clean.
Powerful.

Advanced Example: Packing Slip Posting with User-Selected Batches

Let's build a realistic scenario.
Business requirement:
  • Sales order has multiple inventory transactions
  • User opens a custom screen
  • User selects specific batches
  • Packing slip should post only selected batches
Not all available inventory transactions.

Step 1: Collect User Selection

[ExtensionOf (classStr(SalesPackingSlipJournalPost))]
final public class SlsSalesPackingSlipJournalPostCls_Extension
{
    protected void updateInventoryForLine(
        SalesPackingSlipJournalPostUpdateInventoryLineParameters    _parameters,
        SalesPackingSlipJournalPostPostInventoryState               _postInventoryState)
    {
		using (HpInventUpdCustomTransListContext transSelectListContext = HpInvInventUpdCustomTransListContext::construct())
        {
		    //Just for showcasing, so not declaring 
			while TemporaryTableRecord
			{
				InventTrans inventTransLoc  =  InventTrans::FindRecId(TemporaryTableRecord.InventTransRefRecId);
				
				transSelectListContext.addCustomInventTransToList(inventTransLoc, inventTransLoc.Qty);
			}


			next updateInventoryForLine(_parameters, _postInventoryState);
		}
	}
}
My custom context class:
class HpInvInventUpdCustomTransListContext implements System.IDisposable
{
    static  HpInvInventUpdCustomTransListContext    gCustomTransListContext;
    private List                                    gCustomTransList;

    protected void new()
    {
        gCustomTransListContext = this;
        gCustomTransList = new List(Types::Class);
    }

    public static HpInvInventUpdCustomTransListContext construct()
    {
        return new HpInvInventUpdCustomTransListContext();
    }

    public void dispose()
    {
        gCustomTransListContext = null;
    }

    static public HpInvInventUpdCustomTransListContext current()
    {
        return gCustomTransListContext;
    }

    public void addCustomInventTransToList(
            InventTrans     _inventTrans,
            InventQty       _inventQty)
    {
        InventTrans     inventTransLoc;
        boolean         isReceipt               =   (_inventTrans.direction() == InventDirection::Receipt) ? true : false;
        List            customTransListLoc      =   this.getInventTransSelectList();

        inventTransLoc.data(_inventTrans);

        if (isReceipt)
        {
            InventUpdateCustomTransReceiptListItem  customTransReceiptList  =   InventUpdateCustomTransReceiptListItem::construct();
            customTransReceiptList.inventTrans  =   inventTransLoc;
            customTransReceiptList.inventQty    =   _inventQty;
            customTransListLoc.addEnd(customTransReceiptList);
        }
        else
        {
            InventUpdateCustomTransIssueListItem  customTransIssueList      =   InventUpdateCustomTransIssueListItem::construct();
            customTransIssueList.inventTrans  =   inventTransLoc;
            customTransIssueList.inventQty    =   _inventQty;
            customTransListLoc.addEnd(customTransIssueList);
        }
    }

    public List getInventTransSelectList()
    {
        return gCustomTransList;
    }

}


Here we build the custom inventory transaction list.

Step 2: Inject It Before Posting

[ExtensionOf (classStr(InventUpd_Physical))]
final public class InvInventUpd_PhysicalCls_Extension
{
	public void updateNow(LedgerVoucher _ledgerVoucher)
	{
		HpInvInventUpdCustomTransListContext   transSelectList =   HpInvInventUpdCustomTransListContext::current();
		
		if (transSelectList)
		{
			if (!transSelectList.getInventTransSelectList().empty())
			{
				//This is standard method, which accepts list as parameter
				this.initializeCustomInventTransList(transSelectList.getInventTransSelectList());
			}
			
			//Dispose the list as the we have initialized customInventTransList.
			transSelectList.dispose();
		}
	   
		next updateNow(_ledgerVoucher);
	}
}

Now standard inventory posting framework receives your custom transaction list.

Step 3: Let Standard Logic Do The Rest

Deep inside the inventory framework:
if (this.parmCustomInventTransListInitialized())
{
    this.initializeInventTransToIssueListWithCustomInventTransList(...);
}
else
{
    this.initializeInventTransToIssueListFromDatabase(...);
}


This is the key moment.
If your custom list exists:
✅ Standard code uses your list
❌ Database selection logic is skipped
Which means:
Your selected batch wins.

Standard Selection vs CustomInventTransList

Standard SelectionCustomInventTransList
System decides transactionsDeveloper decides transactions
Uses sorting logicUses explicit transaction list
Hard to control batchesFull batch control
Often requires customizationUses existing framework
Can be unpredictable for business usersPredictable and transparent

Golden Rule

When standard posting selection is wrong, override the selection—not the posting engine.

Reality Check: How Different Roles Think

Junior Developer - "Let me customize the inventory query."
Senior Developer "Can I influence transaction selection before posting starts?"
Architect  "Can I solve this using standard framework extensibility with minimal upgrade impact?"
Notice the difference?
The more experienced you become, the less code you want to own.

Final Thoughts

  • Inventory transactions are often split automatically.
  • Standard posting uses predefined selection logic.
  • Businesses frequently need specific transaction selection.
  • CustomInventTransList provides a standard Microsoft framework for this.
  • No need for heavy posting customizations.
  • Simply provide the transactions you want the framework to use.
  • Standard code will prioritize your list over database selection.
The best customization is not the one that replaces standard logic—it’s the one that guides standard logic to do exactly what the business needs.

Comments