venerdì 28 agosto 2026

D365FFO - Supply Forecasts: Do They Participate in MRP Netting?

Purpose

The purpose of this document is to describe the behavior of the D365FO Supply Forecast functionality when Planning Optimization is configured to include supply forecasts in the Master Plan. Microsoft allows Master Planning to consider supply forecasts through the Include supply forecast option in the Master Plan configuration. 



Configuration

The following configuration was used during testing:

  • Forecast model enabled in the Master Plan
  • Include Supply Forecast = Yes
  • Planning engine = Planning Optimization
  • Coverage planning enabled at inventory dimension level

A Supply Forecast was created for a purchased item and linked to a vendor.

Initial Assumption

The initial expectation was that the Supply Forecast would behave as future available supply and therefore directly offset requirements.

Under this assumption, if a requirement of 80,640 units existed and a Supply Forecast of 80,640 units was entered for the same period, no Planned Purchase Order would be created because the forecast would already satisfy the demand.

Actual Behavior Observed

Testing demonstrated a different behavior.

When the Master Plan includes Supply Forecasts, D365FO does not treat the forecast as an existing supply transaction. Instead, Planning Optimization generates Planned Purchase Orders based on the Supply Forecast quantity.

The generated Planned Purchase Orders contain the indicator:

Supply forecast = Yes

This clearly identifies the order as originating from a Supply Forecast.

Scenario 1: Forecast Quantity Equals Requirement Quantity

Input

  • Supply Forecast = 80,640 units
  • Requirement = 80,640 units

Result

D365FO generated a Planned Purchase Order for 80,640 units.

The Planned Purchase Order was flagged as:

Supply forecast = Yes

Observation

Even though the forecast quantity matched the requirement quantity, the system still generated a Planned Purchase Order.

This demonstrates that the Supply Forecast does not behave as existing supply. Instead, it acts as a planning signal that generates procurement recommendations.

Scenario 2: Forecast Quantity Is Lower Than Requirement Quantity

Input

  • Supply Forecast = 78,640 units
  • Requirement = 80,640 units

Result

D365FO generated:

  • A forecast-based Planned Purchase Order for 78,640 units.
  • Additional planned supply for the remaining shortage.

Observation

The forecast quantity becomes a planned procurement recommendation. When requirements exceed the forecast quantity, Planning Optimization proposes additional supply to cover the remaining demand.

Scenario 3: Forecast Date Is Earlier Than Requirement Date

Input

  • Supply Forecast Date = Day 1
  • Requirement Date = Day 2

Result

The Planned Purchase Order retained the Supply Forecast date.

The order was then pegged against the requirement occurring on the later date.

Observation

Forecast-driven Planned Purchase Orders can cover future requirements while preserving the original forecast date.

Firming Test

The forecast-driven Planned Purchase Order was firmed.

After firming:

  • The Planned Purchase Order became an actual Purchase Order.
  • The quantity and vendor information were retained.
  • The Purchase Order became part of the normal supply plan.

This behavior is consistent with standard D365FO firming functionality, where planned orders are converted into actual orders. 

Functional Interpretation

Based on the tests performed, the Supply Forecast should be interpreted as:

Expected future procurement that should be planned.

The Supply Forecast should not be interpreted as:

  • On-hand inventory
  • An existing Purchase Order
  • A firm supply transaction
  • A confirmed future receipt available for netting

Instead, the Supply Forecast drives the creation of forecast-based planned procurement recommendations.

Conclusion

The testing performed indicates that when Include Supply Forecast is enabled, D365FO Planning Optimization converts Supply Forecast quantities into forecast-based Planned Purchase Orders.

These Planned Purchase Orders are identified by the field:

Supply forecast = Yes

The generated planned orders are subsequently considered during net requirements calculation and can be pegged against future demand.

If actual requirements exceed the forecast quantity, Planning Optimization generates additional planned supply to cover the remaining shortage.

Key Design Principle

Supply Forecasts drive the creation of planned procurement proposals. They are not treated as existing supply transactions that directly offset demand.

giovedì 4 giugno 2026

D365FFO - Flexible negative days in D365FO

 The Basic Concept: Negative Days

Negative days represent the number of days a delivery can be late before the system needs to create a new replenishment order. During this period, the item's inventory level is allowed to go negative. Intra-Cloud Dynamics

In practice, it's the answer to the question: "how long am I willing to wait before ordering again, knowing I already have something inbound?"

Set negative days to zero and the system becomes very reactive — it tends to create new planned orders even when existing supply is already close to the requirement date. Set them too high and you risk accepting delays your customer won't tolerate.


CASE 1 

The Setup

ParameterValue
PO receipt date09/06/2026
PO quantity5 kg
Requirement date04/06/2026
Requirement quantity3 kg
Item lead timeassume 5 days (so earliest replenishment = today 04/06 + 5 = 09/06)
Negative days0

D365FO result CASE 1


The two transactions are settled together.

CASE 2

The Setup

ParameterValue
PO receipt date09/06/2026
PO quantity5 kg
Requirement date04/06/2026
Requirement quantity3 kg
Item lead timeassume 4 days (so earliest replenishment = today 04/06 + 5 = 09/06)
Negative days0

D365FO result CASE2


New planned order created and suggestion to delete the one already firmed.

CASE 3

The Setup

ParameterValue
PO receipt date09/06/2026
PO quantity5 kg
Requirement date04/06/2026
Requirement quantity3 kg
Item lead timeassume 4 days (so earliest replenishment = today 04/06 + 5 = 09/06)
Negative days1

D365FO result CASE3


The two transactions are settled together.

venerdì 29 maggio 2026

D365FFO - Available on location flushing principle

 

Step 1  ·  Open Released Products

Go to Product information management > Products > Released products.

Find and select the finished good item.


Step 2  ·  Open the Engineer menu

On the Action Pane, click Engineer.


Step 3  ·  Open the Formula

Click Edit on the formula header.


   


Click the link in the Formula field to open the formula lines.


Step 4  ·  Enable Available on location

Select the component line you want to configure.

Click the Setup tab.

Switch to Edit mode using the shortcut (pencil icon or Ctrl+Shift+F5).

📌  The 'Available on location' checkbox is on the Setup tab. When enabled, D365FO will generate raw material picking work directly from the production input location, bypassing the standard location directive resolution.

 

Part 2 — Create and Start a Batch Order

Now we create a batch order for the configured item and verify that picking work is generated from the production input location.

 

Step 5  ·  Navigate to Production orders

Go to Production control > Production orders > All production orders.

Step 6  ·  Create a new Batch Order

Click New batch order.

In the Item number field, enter the item.

Click New batch order (confirm the dialog).

Step 7  ·  Fill in Site and Warehouse

In the Site field, enter or select the site (e.g. '1').

Click Yes to confirm site selection.

Close the lookup.

In the Warehouse field, enter or select the WMS warehouse.

Select the row from the list.

Step 8  ·  Set Quantity and Create

In the Quantity field, enter the production quantity.

Click Create.

Part 3 — Start the Order and Verify Work

Step 9  ·  Start the production order

 Find and select the order in the list.

 On the Action Pane, click Production order > Start.

 Click OK to confirm.

Starting the order triggers the release to the warehouse and generates raw material picking work.

 

Step 10  ·  Check Work details

On the Action Pane, click View > Warehouse > Work details.

Verify that the From location on the picking work lines matches the production input location — not a dynamically resolved bin.


Step 11  ·  Review Picking list

On the Action Pane, click View.

Click Picking list.

Part 4 — Complete Picking via Mobile App

The warehouse operator completes the raw material picking using the WMS mobile app. Because 'Available on location' is enabled, the From location is pre-determined — no dynamic location decision is needed.

 

Step 12  ·  Pick on the mobile device

Log in to the Warehouse Management mobile app.

Open the Raw material picking menu item.

Follow the directed picking steps — the app presents the fixed input location as the pick origin.

Part 5 — Confirm Result

Step 13  ·  Verify picking journal is posted

 Reopen the production order.

The related picking list journal is now posted, as shown below.

  The picking journal posted automatically after the mobile app confirmed the pick. The component inventory has been moved from the input location to the production order.

martedì 19 maggio 2026

D365FFO, AX2012 - Contare i record su una tabella

Con questo semplice job è possibile contare il numero di record su una tabella passata come parametro. Il conteggio avviene cross company oppure su determinate company selezionate. L'operazione può essere utile per informazini statistiche. 

In ax 2012 l'informazione è già presente cliccando quì:

 static void LILRecordTableCount(Args _args)  
 {  
   Common    common;  
   DictTable  dt;  
   DataArea   dataArea;  
   int64    totalCount;  
   int64    companyCount;  
   TableName  _tableName = "CustTable";  
   
   
   //metodo 1: usare crossCompany, utile per un conteggio secco  
   dt = new DictTable(tableName2id(_tableName));  
   
   common = dt.makeRecord();  
   
   select crossCompany count(RecId) from common;  
   
   info(strFmt("Totale record su tutte le company: %1",common.recId));  
   
   //metodo 2: usare changeCompany, utile per escludere / includere company nel conteggio  
   if(dt.dataPrCompany())  
   {  
     while select dataArea  
       where dataArea.id != "DAT"  
     {  
       common = null;  
   
       changeCompany(dataArea.Id)  
       {  
         common = dt.makeRecord();  
       
         select count(RecId) from common;  
         companyCount = common.RecId;  
   
         totalCount += companyCount;  
   
         info(strFmt("Company %1: %2 record", dataArea.Id, companyCount));  
       }  
     }  
   }  
   else  
   {  
     common = dt.makeRecord();  
       
     select count(RecId) from common;  
     
     companyCount = common.RecId;  
   
     totalCount = companyCount;  
   }  
   
   info(strFmt("Totale record sulle company selezionate: %1", totalCount));  
 }  
   

lunedì 12 gennaio 2026

D365FFO - Flexible sampling plan (skip lot)

I was eager in the last months to test the new flexible sampling plan (or skip lot for friends). It is a function always required by customers and not easy to implement, if not using vertical solutions.

In order to use it it is required to define the flexible sampling plans in the related form (Inventory management - Setup - Quality control - Flexible sampling plans).


In the example reported above, for the specific plan the test group TG would be applied 3 times in a row for the orders (the status 10 would be reiterated 3 times) and if all the tests are passed the status 20 is reached and the quality orders would be created 1 each 10 orders. The status 20 will be kept till all the quality orders are passed.

In order to apply the specific sampling plan, it is needed to apply it using the quality associations as shown below.


Basically the flag Flexible sampling must be switched on and the Flexible sampling plan code must be defined.

From the flexible sampling plan form it is possible to monitor the status of the sampling level as reported below.



giovedì 8 gennaio 2026

D365FFO - CAPA Management (Corrective and Preventive Action)

After a while I had the chance to test the CAPA management in D365FO. Generally speaking, CAPA cases are managed to keep track of the issues that customer / vendors and employees can raise within the products and processes company related.

In D365FO the CAPA management is available in the Inventory management Menu.



Accessing the CAPA management workspace there are several functions and a list of CAPA cases is available (as reported below).


Accessing one of the cases the following form will be opened.


The CAPA case is managed using a workflow (defined in the field CASE process), using which different actions / tasks are created and assigned to specific users. All the activities are visible accessing the button Activities / View activities.



According to the Case processes emails are sent to the respective assignees of the activities (as reported below).


It is possible to manage as well the electronic signature for the approval / reset status of the activities.

In terms of reports for the feature there is the option to see in a specific period the number of cases by category as shown below.


And a detailed printout per each specific CAPA case.



martedì 23 dicembre 2025

D365FFO - Stampare le informazioni sugli indici delle tabelle

Con questo job è possibile recuperare le informazioni sugli indici e sulla tabelle a cui appartengono.

Questo link è stato molto utile:

https://gist.github.com/mazzy-ax/4d4d06ec2fddd885b67527623467aee8

 internal final class LIL_getIndexInfo  
 {  
   public static void main(Args _args)  
   {  
     str60                        packageName,tableName;  
     CLRObject                      packages, models,tables;  
     CLRObject                      packagesEnumerator, modelsEnumerator,tablesEnumerator;  
     str60                        moduleVersion;  
     DictTable                      dictTable;  
     DictIndex                      dictIndex;      
     int                         i;  
     FieldId                       fieldId;  
     TableId                       tableId;  
     DictField                      dictField;  
     boolean                       isUnique;  
     str                         packageDir;  
     Microsoft.Dynamics.AX.Metadata.MetaModel.ModelInfo modelInfo;  
   
     // Recupera i package  
     packages = Microsoft.Dynamics.Ax.Xpp.MetadataSupport::GetInstalledModuleNames();  
     packagesEnumerator = packages.GetEnumerator();  
       
     // Recupera la directory dei package  
     var environment = Microsoft.Dynamics.ApplicationPlatform.Environment.EnvironmentFactory::GetApplicationEnvironment();  
     packageDir = environment.get_Aos().get_PackageDirectory();  
   
     // Configurazione provider metadati  
     var runtimeProviderConfiguration = New Microsoft.Dynamics.AX.Metadata.Storage.Runtime.RuntimeProviderConfiguration(packageDir);  
     var metadataProviderFactory = New Microsoft.Dynamics.AX.Metadata.Storage.MetadataProviderFactory();  
     Microsoft.Dynamics.AX.Metadata.Providers.IMetadataProvider provider = metadataProviderFactory.CreateRuntimeProvider(runtimeProviderConfiguration);  
     ;  
   
     //loop packages  
     while (packagesEnumerator.moveNext())  
     {  
       packageName = packagesEnumerator.get_Current();  
       models = Microsoft.Dynamics.Ax.Xpp.MetadataSupport::GetModelsInModuleSortedByDisplayName(packageName);  
       modelsEnumerator = models.GetEnumerator();  
         
       //loop models in the package  
       while(modelsEnumerator.MoveNext())  
       {  
         modelInfo = modelsEnumerator.get_Current();  
   
         tables = provider.Tables.ListObjectsForModel(modelInfo.Name);  
         tablesEnumerator = tables.GetEnumerator();  
   
         while(tablesEnumerator.moveNext())  
         {  
           tableName = tablesEnumerator.get_Current();  
   
           if(tableName)  
           {  
             dictTable = new DictTable(tableName2Id(tableName));  
   
             if(dictTable)  
             {  
               for (i = 1; i <= dictTable.indexCnt(); i++)  
               {  
                 dictIndex = new DictIndex(dictTable.id(), dictTable.indexCnt2Id(i));  
   
                 isUnique = !dictIndex.allowDuplicates();  
                   
                 //print table and index info  
                 info(strFmt("Table = %1 | Indice: %2 | Model = %3 | Package = %4 | Modules = %5 | Unique: %6",  
                 tableName,  
                 dictIndex.name(),  
                 modelInfo.Name,  
                 packageName,  
                 dictTable.modules(),  
                 isUnique ? "Yes" : "No"  
                 ));  
               }  
             }  
           }  
         }  
       }  
     }  
   }  
   
 }