Skip to main content

Notifications

Dynamics 365 Community / Forums / Finance forum / Create and release pro...
Finance forum
Unanswered

Create and release products through X++ (AX vs D365)

Posted on by 18
I'm currently adapting a process from AX to D365 that automatically creates sales orders and products based on external integration data. While creating the sales order header through code is straightforward and functions as intended, I'm facing challenges in optimizing the sales order line process, particularly in the product creation/update workflow. The current code seems basic and potentially inefficient given the expanded capabilities of D365. Here's an outline of our process:
  1. Product Master Validation: For a given item number, we first verify whether a product master record already exists. Since this should typically be created in a prior operation, we validate its existence using EcoResProduct and EcoResProductMaster.
     
  2. Product Variant Check: Next, we check if a product variant record (EcoResDistinctProductRecId) exists for the identified product master, using specific inventory dimensions, using EcoResProductVariantDimValue and EcoResProductVariantManager.
     
  3. If both the product master and variant are validated, we proceed to item processing. We check if the item exists in InventTable. If not, we manually release the product (create it and release) by inserting records directly into tables, as seen below. This approach seems complex and potentially inefficient, and I wonder if utilizing an entity, as suggested in various forum posts, would streamline this process (here, here):
    public ItemId releaseProduct(ItemId _itemId, ItemName _itemName, EcoResProductMaster _productMaster)
    {
        ItemId                                  itemId;
        InventTable                             inventTable;
        InventItemSetupSupplyType               inventItemSetupSupplyType;
        EcoResStorageDimensionGroupProduct      storageDimGroupProduct, productDimGroupProduct;
        EcoResTrackingDimensionGroupProduct     trackingDimGroupProduct;
        EcoResStorageDimensionGroupItem         storageDimensionGroupItem;
        EcoResTrackingDimensionGroupItem        trackingDimensionGroupItem;
    
        inventTable.clear();
        inventTable.initValue();
        inventTable.initFromEcoResProduct(_productMaster);
        inventTable.ItemId      = _itemId;
        inventTable.NameAlias   = _itemName;
        if (inventTable.validateWrite())
        {
            inventTable.insert();
        }
    
        InventItemLocation::createDefault(inventTable.ItemId);
    
        inventItemSetupSupplyType.clear();
        inventItemSetupSupplyType.initValue();
        inventItemSetupSupplyType.ItemId            = inventTable.ItemId;
        inventItemSetupSupplyType.ItemDataAreaId    = inventTable.DataAreaId;
        inventItemSetupSupplyType.insert();
    
        storageDimGroupProduct = EcoResStorageDimensionGroupProduct::findByProduct(_productMaster.RecId);
        if (storageDimGroupProduct)
        {
            storageDimensionGroupItem.clear();
            storageDimensionGroupItem.initValue();
            storageDimensionGroupItem.ItemDataAreaId        = inventTable.DataAreaId;
            storageDimensionGroupItem.ItemId                = inventTable.ItemId;
            storageDimensionGroupItem.StorageDimensionGroup = storageDimGroupProduct.StorageDimensionGroup;
            storageDimensionGroupItem.insert();
        }
    
        trackingDimGroupProduct = EcoResTrackingDimensionGroupProduct::findByProduct(_productMaster.RecId);
        if (trackingDimGroupProduct)
        {
            trackingDimensionGroupItem.clear();
            trackingDimensionGroupItem.initValue();
            trackingDimensionGroupItem.ItemDataAreaId           = inventTable.DataAreaId;
            trackingDimensionGroupItem.ItemId                   = inventTable.ItemId;
            trackingDimensionGroupItem.TrackingDimensionGroup   = trackingDimGroupProduct.TrackingDimensionGroup;
            trackingDimensionGroupItem.insert();
        }
    
        itemId = inventTable.ItemId;
        return itemId;
    }
 
Beyond the mentioned product release code, we also set up multiple parameters by affecting tables directly, like item dimension combinations (InventDim and InventDimCombination) , item groups (InventModelGroup, InventModelGroupItem, InventItemGroup, InventItemGroupItem, InventTableModule), item serial number, and the default dimensions (InventItemInventSetup, InventItemPurchSetup, InventItemSalesSetup). My question is: can an entity-based approach manage these setups as well, or do we still need to code these configurations manually?
 
Any advice on optimizing this process, particularly with entities or other best practices, would be greatly appreciated. Thank you!
  • Martin Dráb Profile Picture
    Martin Dráb 229,021 Most Valuable Professional on at
    Create and release products through X++ (AX vs D365)
    Most things can be done by entities, but you must use the right entity for the given purpose. For example, if you're creating serial numbers, you aren't creating items, therefore you won't use EcoResReleasedProductCreationV2Entity for that. You'll use InventItemSerialNumberEntity instead. Similarly, you'll use EcoResReleasedProductVariantV2Entity to create product variants.
  • MSilva Profile Picture
    MSilva 18 on at
    Create and release products through X++ (AX vs D365)
    Hi, sorry, hopefully this reply provides a bit more context!
     
    What I meant is that I understand the EcoResReleasedProductCreationV2Entity will be useful for product creation and release (replacing the code shared in the original question). However, I’m still unsure if the remaining operations I mentioned are fully supported through entities, or if some custom coding will still be required (extending the entity logic / custom methods on the main process). To give more context, after releasing the product, we:
    1. Create Item Inventory Dimensions: We create inventory dimensions on InventDim for characteristics like color and style, then create a new record on InventDimCombination for the given product variant, item, and InventDimId.
    2. Assign Item Groups: We assign item groups (e.g., InventItemGroup, InventModelGroup, InventItemGroupItem, InventModelGroupItem) based on our custom groups. I see that these values can be assigned directly through the entity, which is helpful.
    3. Add Serial Numbers: We insert a record in the InventSerial table to link the ItemId with the custom serial number from an external provider. I assume we can modify this table directly after calling the entity.
    4. Define Inventory Dimension Records and Setup Operations: Finally, we configure inventory dimensions (site, location, warehouse) and perform inventory setup operations (inventory, purchase, and sales setup). I’m uncertain whether this code should be called after calling the entity, or if it’s something manageable within the entity operation, possibly via extension, as you suggested. Here's an example:
      MyMethod()
      {
        void createUpdateInventItem(InventItemOrderSetupMap _inventItemTable, InventDimId _inventDimId, InventDimId _defaultInventDimId) { if (_inventItemTable) { _inventItemTable.InventDimIdDefault = _defaultInventDimId; _inventItemTable.update(); } else { _inventItemTable.clear(); _inventItemTable.ItemId = itemId; _inventItemTable.InventDimId = _inventDimId; _inventItemTable.InventDimIdDefault = _defaultInventDimId; _inventItemTable.insert(); } } companyParameters = CustomCompanyParameters::find(); inventDimAllBlank = InventDim::findOrCreateBlank(); inventDimSite.clear(); inventDimSite.initValue(); inventDimSite.InventSiteId = companyParameters.DefaultInventSiteId; inventDimSite = InventDim::findOrCreate(inventDimSite); inventDimLocation.clear(); inventDimLocation.initValue(); inventDimLocation.InventLocationId = companyParameters.DefaultInventLocationId; inventDimLocation = InventDim::findOrCreate(inventDimLocation); // Invent setup inventItemInventSetup = InventItemInventSetup::findDefault(itemId, true); createUpdateInventItem(inventItemInventSetup, inventDimAllBlank.inventDimId, inventDimSite.inventDimId); inventItemInventSetup.clear(); select firstOnly inventItemInventSetup where inventItemInventSetup.ItemId == itemId && inventItemInventSetup.InventDimId == inventDimSite.inventDimId; createUpdateInventItem(inventItemInventSetup, inventDimSite.inventDimId, inventDimLocation.inventDimId); // Purchase setup inventItemPurchSetup = InventItemPurchSetup::find(itemId, inventDimAllBlank.inventDimId, true); createUpdateInventItem(inventItemPurchSetup, inventDimAllBlank.inventDimId, inventDimSite.inventDimId); inventItemPurchSetup.clear(); select firstOnly inventItemPurchSetup where inventItemPurchSetup.ItemId == itemId && inventItemPurchSetup.InventDimId == inventDimSite.inventDimId; createUpdateInventItem(inventItemPurchSetup, inventDimSite.inventDimId, inventDimLocation.inventDimId); // Sales setup inventItemSalesSetup = InventItemSalesSetup::find(itemId, inventDimAllBlank.inventDimId, true); createUpdateInventItem(inventItemSalesSetup, inventDimAllBlank.inventDimId, inventDimSite.inventDimId); inventItemSalesSetup.clear(); select firstOnly inventItemSalesSetup where inventItemSalesSetup.ItemId == itemId && inventItemSalesSetup.InventDimId == inventDimSite.inventDimId; createUpdateInventItem(inventItemSalesSetup, inventDimSite.inventDimId, inventDimLocation.inventDimId);
      ​​​​​​​
      }
  • Martin Dráb Profile Picture
    Martin Dráb 229,021 Most Valuable Professional on at
    Create and release products through X++ (AX vs D365)
    I'm not sure what you mean by default dimensions. The InventTable.DefaultDimension field contains default financial dimensions, but the your original questions suggests that you actually mean default order settings (e.g. fields from InventItemSalesSetup table), not dimensions.
     
    If you're interested in default dimensions, you won't find them in EcoResReleasedProductCreationV2Entity, but EcoResReleasedProductV2Entity has DefaultLedgerDimensionDisplayValue field for this purpose.
     
    Regarding order settings, don't forget that there may be multiple records related to the same item (e.g. for different sites), therefore it's stored in separate tables (with N:1 relation to an item) and you can use separate entities (e.g. InventProductSpecificOrderSettingsV3Entity) for working with them.
     
    Serial numbers also aren't a property of either products or items. At that level, you can say whether to use serial number tracking, but individual serial numbers aren't a part of a product definition.
     
    I'm not sure what you mean by purchase/sales parameters in the context of item creation. You'll need to give us more details.
     
    Don't forget that you can extend existing entities. For example, if EcoResReleasedProductCreationV2Entity meets your requirements but it lacks a particular field, you can extend the entity and add the field there by yourself. It's much easier and consistent than re-implementing the whole thing.
  • MSilva Profile Picture
    MSilva 18 on at
    Create and release products through X++ (AX vs D365)
    Hi Martin, thanks for your reply.

    I completely agree with your perspective. The bugs, overly complex logic, and general maintenance challenges are exactly why I’d like to rebuild this operation instead of simply porting the code from AX to D365 as it is (I wasn’t the original developer for this).

    From the examples I found online (like the ones I linked), it seems that much of the custom code I shared - such as item parameters, item groups, units, and dimension groups - can be managed through EcoResReleasedProductCreationV2Entity.

    However, although I didn’t include code for the remaining methods I mentioned (I can share it if needed), would I still be able to configure the default dimension, purchase/sales parameters, and item serial numbers through it? Or should I consider other EcoResProduct entities as well?

  • Martin Dráb Profile Picture
    Martin Dráb 229,021 Most Valuable Professional on at
    Create and release products through X++ (AX vs D365)
    Using the existing entity instead of writing custom code for that was an idea that immediately came to me when I began reading your question.
     
    The best code is the one that you don't have to design, write, test and maintain, therefore if you don't have a good reason not to reimplement the logic (and maybe missing some logic already included in the entity), I would avoid it. One of the reasons may be providing simplified but faster logic.
     
    An example of the cost of custom code is a serious bug in your code that may produce invalid data. If validateWrite() fails, you don't create InventTable but you'll still create related records for the ItemId, which is obviously wrong. You should throw an exception or conditionally skip the rest of code. Also, validating the other records too before saving would be a good idea.
     
    By the way, you don't need to call create() on a newly instantiated table buffers.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

News and Announcements

Forum Structure Changes Coming Soon!

Quick Links

Forum Structure Changes Coming on 11/8!

In our never-ending quest to help the Dynamics 365 Community members get answers faster …

Dynamics 365 Community Platform update – Oct 28

Welcome to the next edition of the Community Platform Update. This is a status …

Leaderboard

#1
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 290,734 Super User 2024 Season 2

#2
Martin Dráb Profile Picture

Martin Dráb 229,021 Most Valuable Professional

#3
nmaenpaa Profile Picture

nmaenpaa 101,150

Leaderboard

Featured topics

Product updates

Dynamics 365 release plans