Wednesday, April 2, 2025

Custom business event in D365FO

Implementing Custom Business Events in D365FO

Implementing Custom Business Events in D365FO

In this blog post, we'll walk through the steps to implement custom business events in Dynamics 365 Finance and Operations (D365FO). We'll cover creating a contract, defining a business event class, triggering the business event, building the solution, and rebuilding the business event catalog. Let's dive in!

Step 1: Create the Contract

First, we need to create a contract class that will define the data structure for our business event. Here's an example:


[BusinessEvents(classStr(TestCustTableUpdateBusinessEventContract), 'Test:TestCustTableUpdateBusinessEventContract', 'Test description', ModuleAxapta::AccountsReceivable)]
public class TestCustTableUpdateBusinessEventContract extends BusinessEventsContractBase
{
    CustTable custTable;

    public static TestCustTableUpdateBusinessEventContract newFromCustTable(CustTable _custTable)
    {
        TestCustTableUpdateBusinessEventContract contract = new TestCustTableUpdateBusinessEventContract();
        contract.parmcustTable(_custTable);
        return contract;
    }

    private CustTable parmcustTable(CustTable _custTable = custTable)
    {
        custTable = _custTable;
        return custTable;
    }
}
    

Step 2: Define the Business Event Class

Next, we'll define the business event class that will handle the event logic. Here's an example:


public class TestCustomerDataUpdateBusinessEvent extends BusinessEventsBase
{
    CustTable custTable;

    public static TestCustomerDataUpdateBusinessEvent newFromCustTable(CustTable _custTable)
    {
        TestCustomerDataUpdateBusinessEvent businessEvent = new TestCustomerDataUpdateBusinessEvent();
        businessEvent.parmcustTable(_custTable);
        return businessEvent;
    }

    private CustTable parmcustTable(CustTable _custTable = custTable)
    {
        custTable = _custTable;
        return custTable;
    }

    [Wrappable(true), Replaceable(true)]
    public BusinessEventsContract buildContract()
    {
        // Note: This method will be called only if the business event is activated.
        return TestCustTableUpdateBusinessEventContract::newFromCustTable(custTable);
    }
}
    

Step 3: Trigger the Business Event

Finally, we'll trigger the business event from the CustTable update method. Here's how you can do it:


[ExtensionOf(tableStr(CustTable))]
final class TestCustTable_Extension
{
    void update(boolean _updateSmmBusRelTable, boolean _updateParty)
    {
        next update(_updateSmmBusRelTable, _updateParty);

        // Call business event
        if (BusinessEventsConfigurationReader::isBusinessEventEnabled(classStr(TestCustomerDataUpdateBusinessEvent)))
        {
            CustTable custTable = CustTable::find(this.AccountNum);
            TestCustomerDataUpdateBusinessEvent::newFromCustTable(custTable).send();
        }
    }
}
    

Step 4: Build the Solution

After making the necessary changes, build the solution to ensure that all components are correctly compiled and integrated.

Step 5: Rebuild the Business Event Catalog

Now, go to System administration > Setup > Business events > Business event catalog form and rebuild the business event catalog. This step ensures that your new business event is registered and available for use.

Conclusion

By following these steps, you can implement custom business events in D365FO. This allows you to extend the functionality of your system and integrate with external systems or processes. Happy coding!

Tuesday, April 1, 2025

How to add Personnel Number field on the data entity D365FO

How to Add Personnel Number Field on the Data Entity in D365FO

How to Add Personnel Number Field on the Data Entity in D365FO

Follow these steps to add the Personnel Number field to the CustCustomerV3Entity data entity in Dynamics 365 Finance and Operations.

Step 1: Create Extension for CustCustomerV3Entity

Step 2: Add New Fields

Add two new fields: TestHcmWorkerRecId,TestPersonnelNumber.

Repeat this step for the staging table as well.

Step 3: Create Code Extension for CustCustomerV3Entity

Create a new code extension for CustCustomerV3Entity and add the following code:

[ExtensionOf(tableStr(CustCustomerV3Entity))] final class TESTCustCustomerV3Entity_Extension { /// <summary> /// Provides the query to be used to compute the value of TestPersonnelNumber field. /// </summary> /// <returns>A query to be used to compute the value of TestPersonnelNumber field.</returns> public static str testCollectionAgent() { return smmUtility::workerPersonnelNumberQuery( tablestr(CustCustomerV3Entity), dataEntityDataSourceStr(CustCustomerV3Entity, CustTable), fieldstr(CustTable, TestHcmWorkerRecId)); } public void mapEntityToDataSource(DataEntityRuntimeContext _entityCtx, DataEntityDataSourceRuntimeContext _dataSourceCtx) { if (_dataSourceCtx.name() == dataEntityDataSourceStr(CustCustomerV3Entity, CustTable)) { this.TestHcmWorkerRecId = smmUtility::getEntityWorkerRecId(this.testCollectionAgent); } next mapEntityToDataSource(_entityCtx, _dataSourceCtx); } }

Step 4: Configure Field Properties

On the TestPersonnelNumber field, set the DataEntityViewMethod property to TESTCustCustomerV3Entity_Extension::testCollectionAgent.

Step 5: Build and Synchronize

Build and synchronize the project.

Step 6: Test Using Postman

Test the changes using Postman to ensure everything is working correctly.

How to register purchase orders in D365FO using X++ code

Registering a Purchase Order in X++

Code Explanation


internal final class TESTJobToCheckRegistraion
{
    PurchFormLetterParmData          purchFormLetterParmData;
    PurchParmTable                   purchParmTable;
    PurchParmLine                    purchParmLine;
    PurchParmUpdate                  purchParmUpdate;
    PurchTable                       purchTable;
    PurchFormLetter                  purchFormLetter;
    TransDate                        packingSlipDate;
    PackingSlipId                    packingSlipId;

    public static void main(Args _args)
    {
        PurchTable    purchTable;
        PurchLine     purchLine;
        TransDate     packingSlipDate;
        PackingSlipId packingSlipId;
        container     lineNums;
        container     purchLineRecIds;
        container     inventBatchIds;
        container     qtys;
        int           i;

        purchTable      = PurchTable::find('002357-POR-UK01');
        packingSlipDate = DateTimeUtil::date(DateTimeUtil::utcNow());
        packingSlipId   = 'PK0001';

        lineNums       = [100]; // purch line numbers
        inventBatchIds = ['Batch001']; //batch numbers to register
        qtys           = [100]; // quantity to register

        for (i = 1; i <= conLen(lineNums); i++)
        {
            purchLine = PurchLine::find(purchTable.PurchId, conPeek(lineNums, i));
            purchLineRecIds = conIns(purchLineRecIds, conLen(purchLineRecIds) + 1, purchLine.RecId);
        }

        TESTJobToCheckRegistraion TESTJobToCheckRegistraion = new TESTJobToCheckRegistraion();
        TESTJobToCheckRegistraion.insertParmLineData(purchLineRecIds, inventBatchIds, qtys);
    }

    private void registerInventory(PurchParmLine _purchParmline, InventDim _inventDim)
    {
        InventTransWMS_Register inventTransWMS_register;
        TmpInventTransWMS       tmpInventTransWMS;
        InventDim               inventBatchCheck;
        InventTrans             inventTranslocal;
        InventDim               inventDimlocal;

        inventTranslocal = InventTrans::findTransId(_purchParmline.InventTransId, true);
        inventDimlocal   = inventTranslocal.inventDim();
        inventDimlocal.inventBatchId    = _inventDim.inventBatchId;
        inventDimlocal.InventLocationId = _inventDim.InventLocationId;
        inventDimlocal.InventSiteId     = _inventDim.InventSiteId;
        inventDimlocal                  = InventDim::findOrCreate(inventDimlocal);
        inventTransWMS_register         = inventTransWMS_register::newStandard(tmpInventTransWMS);

        inventTranslocal.inventDimId    = inventDimlocal.inventDimId;
        tmpInventTransWMS.clear();
        tmpInventTransWMS.initFromInventTrans(inventTranslocal);
        tmpInventTransWMS.ItemId    = inventTranslocal.ItemId;
        tmpInventTransWMS.InventQty = _purchParmline.ReceiveNow;
        tmpInventTransWMS.insert();

        inventTransWMS_register.writeTmpInventTransWMS(tmpInventTransWMS, inventTranslocal, inventDimlocal);
        inventTransWMS_register.updateInvent(inventTranslocal);
    }

    private void insertParmLineData(container _purchLineRecIds, container _inventBatchIds, container _qtys)
    {
        PurchLine purchLine;
        InventDim inventDim;
        int       i;

        for (i = 1; i <= conLen(_inventBatchIds); i++)
        {
            purchLine.clear();
            purchLine = PurchLine::findRecId(conPeek(_purchLineRecIds, i));
            purchParmLine.InitFromPurchLine(purchLine);
            inventDim = purchLine.inventDim();
            purchParmLine.ReceiveNow  = decRound(conPeek(_qtys, i), 2);
            purchParmLine.InventDimId = inventDim.inventDimId;
            purchParmLine.ParmId      = purchParmTable.ParmId;
            purchParmLine.TableRefId  = purchParmTable.TableRefId;
            purchParmLine.setQty(DocumentStatus::PackingSlip, false, true);
            purchParmLine.setLineAmount();
            purchParmLine.insert();
            inventDim.inventBatchId   = conPeek(_inventBatchIds, i);
            inventDim = inventDim::findOrCreate(inventDim);
            this.registerInventory(purchParmLine, inventDim);
        }
    }
}

   


Saturday, March 29, 2025

After product receipt from x++ code system is not creating project transactions

Resolving Project Transactions Issue in X++

Resolving Issue: Project Transactions Not Created After Product Receipt in X++

Introduction

When receiving a product receipt in Microsoft Dynamics 365 Finance and Operations via X++ code, you might encounter a scenario where project transactions are not generated, even when a valid project ID is associated with the purchase order. Debugging revealed that the system is not calling the runProjectPostings() method of the PurchFormLetter_PackingSlip class, which prevents the project packing slip from being posted. To resolve this, I created the runRemainProjectUpdates method in the code below.

Code Implementation

Product Receipt Creation


private TestPOReceiptResponseContract createProductReceipt(TestPOReceiptRequestContract _requestContract)
{
    PurchFormLetter purchFormLetter;
    PurchParmTable purchParmTable;
    PurchParmLine purchParmLine;
    PurchFormletterParmData purchFormLetterParmData;
    TestOrderLineRequestContract orderLineContract = _requestContract.parmOrderLineContract();
    PurchTable purchTable = PurchTable::find(orderLineContract.parmPurchaseOrderId());
    PurchLine purchLine = PurchLine::find(purchTable.PurchId, orderLineContract.parmLineNumber());

    purchFormLetterParmData = PurchFormletterParmData::newData(DocumentStatus::PackingSlip, VersioningUpdateType::Initial);
    purchFormLetterParmData.parmOnlyCreateParmUpdate(true);
    purchFormLetterParmData.createData(false);

    PurchParmUpdate purchParmUpdate = purchFormLetterParmData.parmParmUpdate();

    purchParmTable.clear();
    purchParmTable.TransDate = _requestContract.parmProducReceiptDate();
    purchParmTable.Ordering = DocumentStatus::PackingSlip;
    purchParmTable.ParmJobStatus = ParmJobStatus::Waiting;
    purchParmTable.Num = _requestContract.parmCoupaId();
    purchParmTable.PurchId = purchTable.PurchId;
    purchParmTable.insert();
    
    purchParmLine.initFromPurchLine(purchLine);
    purchParmLine.ReceiveNow = _requestContract.parmTotal();
    purchParmLine.ParmId = purchParmTable.ParmId;
    purchParmLine.insert();

    purchFormLetter = PurchFormLetter::construct(DocumentStatus::PackingSlip);
    purchFormLetter.transDate(_requestContract.parmProducReceiptDate());
    purchFormLetter.run();

    // Generate project item transactions
    if (purchTable.ProjId != '')
    {
        this.runRemainProjectUpdates(purchFormLetter, purchParmTable.ParmId);
    }
    
    return this.getResponseContract("@ENG_Labels:ProductReceiptCreated", true);
}
    

Explicitly Calling runRemainProjectUpdates to Create Project Transactions


public void runRemainProjectUpdates(PurchFormLetter _purchFormLetter, ParmId _parmId)
{
    FormletterOutputContract outputContract = _purchFormLetter.getOutputContract();
    VendPackingSlipVersion localVendPackingSlipVersion;
    VendPackingSlipJour localVendPackingSlipJour;
    VendPackingSlipTrans localVendPackingSlipTrans;
    PurchLine localPurchLine;
    SalesLine localSalesLine;
    SalesFormLetter salesFormLetter;

    if (ProjParameters::find().AutomaticItemConsumption == NoYes::Yes)
    {
        select firstonly RecId, AccountingDate from localVendPackingSlipVersion
            where localVendPackingSlipVersion.ParmId == _parmId
            exists join localVendPackingSlipJour
                where localVendPackingSlipVersion.VendPackingSlipJour == localVendPackingSlipJour.RecId
                exists join localVendPackingSlipTrans
                    where localVendPackingSlipTrans.VendPackingSlipJour == localVendPackingSlipJour.RecId
                    exists join localPurchLine
                        where localPurchLine.InventTransId == localVendPackingSlipTrans.InventTransId
                        && localPurchLine.ItemRefType == InventRefType::Sales
                        exists join localSalesLine
                            where localSalesLine.InventTransId == localPurchLine.InventRefTransId
                            && localSalesLine.ProjId;

        if (localVendPackingSlipVersion.RecId)
        {
            salesFormLetter = SalesFormLetter::newFromPurchFormLetter_PackingSlip(
                SysOperationHelper::base64Encode(outputContract.parmJournalLinesPacked()),
                DocumentStatus::ProjectPackingSlip);

            if (localVendPackingSlipVersion.AccountingDate)
            {
                salesFormLetter.transDate(localVendPackingSlipVersion.AccountingDate);
            }

            salesFormLetter.runOperation();
        }
    }
}
    

How to register return sales order lines using X++ code

How to Register Return Sales Order Lines in X++

The TestSalesReturnOrderLineRegister Class in X++

The TestSalesReturnOrderLineRegister class serves as an entry point to the product registration process, specialized for the sales return order process. It handles the return disposition code and manages sales return order specifics, such as creating inventory transactions.

Code Overview



class TestSalesReturnOrderLineRegister extends TradeOrderLineRegister
{
    ReturnDispositionCodeId returnDispositionCodeId;
    DialogField dialogDispositionCodeId;
    SalesLine salesLine;
    boolean reverseInventTrans;
    boolean recreateReservationLine;
    boolean inRegistration;

    public boolean init()
    {
        boolean ret = super();
        salesLine = salesPurchLine;
        return ret;
    }

    public void run()
    {
        this.runPreSuper();
        super();
        this.runPostSuper();
    }

    public void runPostSuper()
    {
        salesLine = SalesLine::findInventTransId(salesLine.InventTransId, true);
        if (inRegistration)
        {
            if (salesLine.ReturnStatus == ReturnStatusLine::Awaiting)
            {
                if (reverseInventTrans && !salesLine.ReturnAllowReservation)
                {
                    SalesLine::changeReturnOrderType(salesLine.InventTransId, null, true);
                    salesLine = SalesLine::findInventTransId(salesLine.InventTransId, true);
                }
                if (salesLine.ReturnDispositionCodeId)
                {
                    salesLine.ReturnDispositionCodeId = '';
                    salesLine.update();
                }
            }
        }
        else 
        {
            if (salesLine.ReturnStatus == ReturnStatusLine::Registered && recreateReservationLine)
            {
                salesLine.createReturnReservationLine();
                salesLine = SalesLine::findInventTransId(salesLine.InventTransId, true);
            }
        }
    }
}
    

Calling the TestSalesReturnOrderLineRegister Class


public void registerReturnSOLine(SalesId _salesId, ReturnDispositionCodeId _dispositionCode)
{
    TradeOrderLineRegister tradeOrderlineRegister;
    TestSalesReturnOrderLineRegister salesReturnOrderLineRegister;
    InventTransWMS_Register inventTransWMS_register;
    TmpInventTransWMS TmpInventTransWMS;
    SalesLine salesLine;
    InventTrans inventTrans;
    InventTransOrigin inventTransOrigin;
    returnDispositionCode returnDispositionCode;

    Args args = new Args();
    while select forUpdate salesLine where salesLine.SalesId == _returnSOTable.SalesId
    {
        args.record(salesLine);
        tradeOrderLineRegister = TestSalesReturnOrderLineRegister::construct();
        tradeOrderLineRegister.parmArgs(args);
        tradeOrderLineRegister.init();
        salesReturnOrderLineRegister = tradeOrderLineRegister;
        salesReturnOrderLineRegister.runPreSuper();
        
        select firstOnly returnDispositionCode
            where returnDispositionCode.DispositionAction == DispositionAction::Credit;
        
        salesLine.ReturnDispositionCodeId = returnDispositionCode.DispositionCodeId;
        salesLine.update();

        select firstOnly crossCompany inventTrans
            join RecId, InventTransId from inventTransOrigin
                where inventTransOrigin.InventTransId == salesLine.InventTransId
                && inventTrans.InventTransOrigin == inventTransOrigin.RecId;

        inventTransWMS_register = inventTransWMS_register::newStandard(tmpInventTransWMS);
        tmpInventTransWMS.clear();
        tmpInventTransWMS.initFromInventTrans(inventTrans);
        tmpInventTransWMS.InventDimId = inventTrans.InventDimId;
        tmpInventTransWMS.insert();

        inventTransWMS_register.writeTmpInventTransWMS(tmpInventTransWMS, inventTrans, inventTrans.inventDim());
        if (!inventTransWMS_register.updateInvent(salesLine))
        {
            throw error("Error during registration of sales line.");
        }
    }
}
    

Wednesday, March 26, 2025

How to create COC for form methods , How to get formRun , How to make control visible false on form in D365FO

How to create COC for form method in D365FO

How to create COC for form method in D365FO

Example Code

[ExtensionOf(formStr(ProjInvoiceListPage))]
final class TestProjInvoiceListPage_Extension
{
    public void init()
    {
        next init();

        FormRun formRun = this as FormRun;

        if(ProjParameters::find().TestPrintPGGDesignForProjectInvoice)
        {
            FormControl         stdViewCopyBtn   = formrun.design(0).controlName('PrintProjInvoiceCopyButton');
            FormControl         stdViewOrigBtn   = formrun.design(0).controlName('PrintProjInvoiceButton');
            stdViewCopyBtn.visible(false);
            stdViewOrigBtn.visible(false);
        }
        else
        {
            FormControl         customViewCopyBtn   = formrun.design(0).controlName('TestPSAProjInvoiceCopy');
            FormControl         customViewOrigBtn   = formrun.design(0).controlName('TestPSAProjInvoiceOriginal');

            customViewCopyBtn.visible(true);
            customViewOrigBtn.visible(true);
        }
    }
}

Thursday, February 6, 2025

Fix Dynamics 365FO Synchronization Error - Transaction Log Full

Fix Dynamics 365FO Synchronization Error - Transaction Log Full

Fix: The transaction log for database is full due to 'log_backup' - Dynamics 365FO

If you're encountering the error "The transaction log for database is full due to 'log_backup'" while working with Dynamics 365 Finance and Operations (D365FO), it means your SQL Server transaction log has reached its maximum capacity.

Solution

Follow these steps to resolve the issue:

Step 1: Check the Database Files

Run the following SQL query to check the database file details:

    SELECT * FROM sys.database_files
    

Note down the log file name from the result and use that same name in the second statement of the step 2 SQL query.

Query result screenshot:

Database files query result

Step 2: Shrink the Transaction Log

Execute the following commands to shrink the transaction log and free up space:

    ALTER DATABASE AxDB SET RECOVERY SIMPLE;
    DBCC SHRINKFILE('AxDb_New_log', 0, TRUNCATEONLY);
    ALTER DATABASE AxDB SET RECOVERY FULL;
    

Replace 'AxDb_New_log' with the actual log file name you noted earlier.

Step 3: Start DB synchronization again...this time it should work

..

Follow "Dynamics World" blog also for some other approach

Tip: Always ensure you have proper database backups before making changes to recovery models.

Update Values During Data Entity Import in D365FO

Update Values During Data Entity Import in D365FO Updating Field Values During Data Entity Import in Dynamics 365 Finance ...