Monday, May 12, 2025

How to Create Multiselection Lookup in D365FO

How to Create Multiselection Lookup in D365FO

How to Create Multiselection Lookup in D365FO

In this blog post, we will walk through the steps to create a multiselection lookup in Dynamics 365 for Finance and Operations (D365FO). We will use a sample code to demonstrate the process. Also, you can refer to the standard class 'ENGIEMultiSelectionLookupUIBuilder'.

Step 1: Create the Data Contract Class

First, we need to create a data contract class. This class will hold the list of project IDs that we want to select.


internal final class DemoMultiSelectionLookupContract implements SysOperationValidatable, SysOperationInitializable
{
    private List projIdList;

    [DataMemberAttribute("ProjectId"), 
    AifCollectionTypeAttribute("ProjectId", Types::String),
    SysOperationLabelAttribute(literalStr("@SYS103748")),
    SysOperationDisplayOrderAttribute('7')]
    public List parmProjIdList(List _listProjId = projIdList)
    {
        projIdList = _listProjId;
        return projIdList;
    }
    
    public void initialize()
    {

    }
}
    

Step 2: Create the UI Builder Class

Next, we create a UI builder class to add the dialog field for the project ID selection.


public final class DemoMultiSelectionLookupUIBuilder extends SysOperationAutomaticUIBuilder
{
    protected DialogField addDialogField(IdentifierName _methodName, Object _dataContract = this.dataContractObject())
    {
        DialogField dialogField;

        switch (_methodName)
        {
            case methodStr(DemoMultiSelectionLookupContract, parmProjIdList):
                projIdField = this.dialog().addField(extendedTypeStr(ProjId), "@SYS103748", "Select project id those invoice want to export");
                dialogField = projIdField;
                break;

            default:
                dialogField = super(_methodName, _dataContract);
                break;
        }

        return dialogField;
    }

    public void postRun()
    {
        DemoMultiSelectionLookupContract dataContract = this.dataContractObject() as DemoMultiSelectionLookupContract;
        container selectFieldSources = this.selectField();
        super();

        ctrlProjId = SysLookupMultiSelectCtrl::constructWithQueryRun(
            this.dialog().formRun(),
            projIdField.control(),
            new QueryRun(queryStr(ProjTable)),
            true,
            selectFieldSources);
    }

    public container selectField()
    {
        return [tableNum(ProjTable), fieldNum(ProjTable, ProjId)];
    }

   
}
    

Step 3: Create the Service Class

Finally, we create a service class to handle the business logic for the multiselection lookup.


internal final class DemoMultiSelectionLookupService extends SysOperationServiceBase
{
    System.Exception ex;
    internal void runExport(DemoMultiSelectionLookupContract _exportAttachmentsContract)
    {
        try
        {
            List projectList = _exportAttachmentsContract.parmProjIdList();
        }
        catch (ex)
        {
            this.logException(ex);
        }
    }
}
    

Step 4: Create the Controller Class

Finally, we create a controller class to call the service class.


public class DemoMultiSelectionLookupController extends SysOperationServiceController implements BatchRetryable
{
    public static void main(Args _args)
    {
        DemoMultiSelectionLookupController controller = DemoMultiSelectionLookupController::construct();
        controller.parmDialogCaption("@ApplicationFoundation:ExportAttachments");
        
        controller.startOperation();
    }

    public static DemoMultiSelectionLookupController construct(SysOperationExecutionMode _executionMode = SysOperationExecutionMode::Synchronous)
    {
        return new DemoMultiSelectionLookupController(
            classStr(DemoMultiSelectionLookupService),
            methodStr(DemoMultiSelectionLookupService, runExport),
            _executionMode);
    }

    [Hookable(false)]
    public final boolean isRetryable()
    {
        return false;
    }
}
    

And that's it! You now have a multiselection lookup in D365FO. This example demonstrates how to create a data contract, UI builder, service class, and controller class to achieve this functionality.

Friday, April 11, 2025

How to show default value or first value for the field which is not part of group and returning more than one values

Grouping Data by Certain Fields and Handling Cases Where a Specific Field Returns More Than One Value in D365FO SSRS Reports

Grouping Data by Certain Fields and Handling Cases Where a Specific Field Returns More Than One Value in D365FO SSRS Reports

When working with SQL Server Reporting Services (SSRS) in Dynamics 365 Finance and Operations (D365FO), you might encounter scenarios where you need to group data by certain fields and handle cases where a specific field, not part of the group, returns more than one value. In this blog post, we'll explore how to manage such scenarios by conditionally displaying default values for that field.

Scenario

Consider a report with the following fields:

  • Field 1
  • Field 2
  • Field 3
  • Field 4

We want to group the data by certain fields: Field 2, Field 3, and Field 4. However, if a specific Field 1 that is not part of the group returns more than one value, in that case we want to display a default value, such as a blank or "Unknown".

Solution

To achieve this, we can use an SSRS expression to check if there are multiple values for the specific field within the group and conditionally display a default value.

Expression for Blank Default Value

=IIF(CountDistinct(Fields!Field1.Value, "YourGroupName") > 1, "", Fields!Field1.Value)

Expression for "Unknown" Default Value

=IIF(CountDistinct(Fields!Field1.Value, "YourGroupName") > 1, "Unknown", Fields!Field1.Value)

Expression for FirstValue

=First(Fields!Field1.Value, "YourGroupName")

Conclusion

By using these approaches, you can effectively manage the display of specific fields in your D365FO SSRS reports when dealing with multiple values within a group, ensuring that your report remains clear and easy to read.

We hope this solution helps you in your D365FO SSRS reporting tasks. If you have any questions or need further assistance, feel free to reach out!

Wednesday, April 9, 2025

How to Add $filter, $top, $select, cross-company=true and $orderby in in OData URL

How to Add $filter, $top, cross-company=true, and $select in OData URL

How to Add $filter, $top, $select, cross-company=true and $orderby in in OData URL

OData URLs in Dynamics 365 Finance and Operations (D365FO) provide a flexible way to query data. In this blog post, we will demonstrate how to use the $filter, $top, cross-company=true,$select and $orderby query options to retrieve specific records based on certain criteria.

Using $filter

The $filter query option allows you to specify criteria to filter the data returned by the OData service also you need to add "and" operator between 2 filter values and "&" between 2 diffrent options ($filter, $orderby). Below is an example:

{{resource}}/data/PurchaseOrderHeadersV2?$filter=PurchaseOrderNumber eq 'PO12345' and dataAreaId eq 'USMF'&Cross-company=true

This URL filters the PurchaseOrderHeadersV2 entity to only include records where PurchaseOrderNumber is 'PO12345' and dataAreaId is 'USMF', and includes data from all companies.

Using $top

The $top query option specifies the maximum number of records to return in the response. It can be combined with other query options like $filter and $orderby. Below is an example:

{{resource}}/data/VendPackingSlipTransBiEntities?$top=2&$filter=OrigPurchid eq 'PO12345' and dataAreaId eq 'USMF'&Cross-company=true

This URL returns the top 2 records from the VendPackingSlipTransBiEntities entity where OrigPurchid is 'PO12345' and dataAreaId is 'USMF', ordered by DeliveryDate in descending order, and includes data from all companies.

Using $select

The $select query option allows you to specify which properties to include in the response. This can help reduce the amount of data returned and improve performance. Below is an example:

{{resource}}/data/VendPackingSlipTransBiEntities?$select=dataAreaId,OrigPurchid,PackingSlipId&$top=2&$filter=OrigPurchid eq 'PO12345' and dataAreaId eq 'USMF'&Cross-company=true

This URL returns only the dataAreaId, OrigPurchid, and PackingSlipId properties from the VendPackingSlipTransBiEntities entity where OrigPurchid is 'PO12345' and dataAreaId is 'USMF', and includes data from all companies.

Using $orderby

The $orderby query option allows you to sort the data returned by the OData service based on specified properties. Below is an example:

{{resource}}/data/VendPackingSlipTransBiEntities?$top=2&$filter=OrigPurchid eq 'PO12345' and dataAreaId eq 'USMF'&$orderby=DeliveryDate desc&Cross-company=true

This URL returns the top 2 records from the VendPackingSlipTransBiEntities entity where OrigPurchid is 'PO12345' and dataAreaId is 'USMF', ordered by DeliveryDate in descending order, and includes data from all companies.

Conclusion

Using OData URLs to query data in D365FO is a powerful and flexible method to retrieve the exact records you need. The $filter, $top, cross-company=true, $select, and $orderby query options allow you to refine your queries and optimize data retrieval. By combining these options, you can efficiently manage and retrieve data to meet your specific requirements.

Thursday, April 3, 2025

Project Hour Journal Creation and Invoice Posting using x++ code

Project Hour Journal Creation and Invoice Posting

Project Hour Journal Creation, posting , Invoice proposal creation and Posting invoice

Step-by-Step Guide

1. Creating the Project Hour Journal

The first step is to create a project hour journal. This involves initializing the journal table and transaction data, setting various properties, and validating the entries before inserting them into the database.


public journalId createJournal()
{
   
    ProjJournalTable jourTable;
    ProjJournalTableData jourTableData;
    ProjJournalTransData jourTransData;
    ProjJournalStatic jourStatic;
    ProjTable projtable;
    Qty qty;
    ProjLinePropertySetup projLinePropertySetup;
    TestSFIntegrationParameters TestSFIntegrationParameters = TestSFIntegrationParameters::find(); // Custom table.
    ProjLineProperty projLineProperty;
    ProjJournalTrans jourTrans;
    Amount totalAmount;
    JournalId journalId;

  
    select firstonly reverse projLinePropertySetup
        join projLineProperty
            where projLineProperty.LinePropertyId == projLinePropertySetup.LinePropertyId
            && projLinePropertySetup.CategoryRelation == TestSFIntegrationParameters.RevenueCategory
            && projLinePropertySetup.ProjCode == TableGroupAll::All;

    if (!projLineProperty.ToBeInvoiced)
    {
        throw error(strFmt("The chargeable line property is missing for the specified hour category '%1'.", TestSFIntegrationParameters.RevenueCategory));
    }

    jourTableData = JournalTableData::newTable(jourTable);
    jourTable.JournalId = jourTableData.nextJournalId();
    jourTable.JournalType = ProjJournalType::Hour;
    jourTable.JournalNameId = TestSFIntegrationParameters.RevenueJournalNameId;
    jourTableData.initFromJournalName(ProjJournalName::find(jourTable.JournalNameId));
    jourTable.Description = "Header description";
    jourStatic = jourTableData.journalStatic();

    if (jourtable.validatewrite())
    {
        jourtable.insert();
    }
    else
    {
        throw error("Validation failed to create journal header");
    }

    // Create journal transactions
    if (jourtable)
    {
        jourTransData = jourStatic.newJournalTransData(jourTrans, jourTableData);
        projtable = Projtable::find("ProjId0001");

        jourTrans.clear();
        jourTransData.initFromJournalTable();
        jourTrans.initValue();
        jourTrans.ProjId = projtable.ProjId;
        jourTrans.initFromProjTable(projtable);
        jourTrans.TransDate = DateTimeUtil::getSystemDate(DateTimeUtil::getUserPreferredTimeZone());
        jourTrans.ProjTransDate = jourTrans.TransDate;
        jourTrans.SalesPrice = 20.0;
        jourTrans.Txt = "Line description";
        jourTrans.Qty = 200;
        jourTrans.CurrencyId = 'CAN';
        jourTrans.CategoryId = TestSFIntegrationParameters.RevenueCategory;
        jourTrans.LinePropertyId = projLinePropertySetup.LinePropertyId;
        jourTrans.TaxItemGroupId = TestSFIntegrationParameters.TaxItemGroupId;

        if (jourTrans.validateWrite())
        {
            jourTransData.create(false);
            qty += jourTrans.Qty;
        }
        else
        {
            throw error("@ENG_Labels:ValidationFailedToCreateJournalLines");
        }

        jourTable.NumOfLines = real2int(jourTrans.LineNum);
        jourTable.ProjQty = qty;
        jourTable.doUpdate();
    }
    return jourTable.JournalId;
}
    

2. Posting the Journal

Once the journal is created, the next step is to post it. This ensures that the journal entries are finalized and recorded in the system.


public void postJournal(JournalId _journalId)
{
    ProjJournalCheckPost jourPost;
    jourPost = ProjJournalCheckPost::newJournalCheckPost(true, true, JournalCheckPostType::Post, tableNum(ProjJournalTable), _journalId);
    jourPost.runOperation();
}
    

3. Generating the Invoice Proposal

After posting the journal, we generate an invoice proposal based on the journal entries. This involves selecting the appropriate funding source and creating the proposal data.


public ProjProposalId invoiceProposalCreation(JournalId _journalId)
{
    ProjJournalTrans projJournalTrans;
    ProjTable projTable, parentprojTable;
    ProjFundingSourceRefId fundingSource;
    ProjFundingSource ProjFundingSource;
    ProjProposalId proposalId;

    select firstonly ParentId, defaultfundingsource, ProjInvoiceProjId from projTable
        join ProjId from projJournalTrans
            where projTable.ProjId == projJournalTrans.ProjId
               && ProjJournalTrans.JournalId == _journalId
               && ProjJournalTrans.LineNum == 1;

    select firstonly ProjInvoiceProjId, ProjGroupId, defaultfundingsource from parentprojTable
        where parentprojTable.ProjId == projTable.ParentId;

    fundingSource = projTable.DefaultFundingSource != 0 ? projTable.DefaultFundingSource : parentprojTable.DefaultFundingSource;

    if (!fundingSource)
    {
        select firstonly projFundingSource
            where projFundingSource.ContractId == projTable.ProjInvoiceProjId
            && projFundingSource.CustAccount == projTable.CustAccount
            && projFundingSource.FundingType == ProjFundingType::Customer;

        if (!projFundingSource)
        {
            throw error("Couldn't find funding source (Invoice account) for given parent and child project and on assigned contract.");
        }
        fundingSource = projFundingSource.RecId;
    }

    proposalId = TestCreateInvoiceProposal::createDataInTmp(fundingSource, _journalId);
    return proposalId;
}
    

4. Posting the Invoice Proposal

Finally, we post the invoice proposal to generate the actual invoice. This involves updating the proposal journal and running the form letter operation.


public InvoiceId postInvoiceProposal(ProjProposalId _proposalId)
{
    ProjProposalJour projProposalJour;
    ProjFormLetter projFormLetter;

    select forupdate projProposalJour order by RecId desc
        where projProposalJour.ProposalId == _proposalId;

    projProposalJour.PSAInvoiceTxtPre = "Invoice header text";
    projProposalJour.LineProperty = ProjLinePropertyCode::Approved;
    projProposalJour.editInvReportFormat(true, "PSAProjInvoice.Report");
    projProposalJour.update();

    projFormLetter = ProjFormLetter::construct(DocumentStatus::ProjectInvoice);
    projFormLetter.createParmLine(projProposalJour);
    projFormLetter.runOperation();

    projProposalJour = projProposalJour::find(projProposalJour.ProposalId);
    return projProposalJour.ProjInvoiceId;
}
    

Additional Class: TestCreateInvoiceProposal

Below is the additional class code for creating invoice proposals:


class TestCreateInvoiceProposal extends projInvoiceProposalInsertLines
{

    public void createInvoiceProposals(PSATmpProjProposalTrans _tmpProjProposalTrans)
    {
        int i;
        ProjProposalJour proposalJour;
        ProjTable projTable;

        this.progressInit("@SYS54552", 0);
        while select _tmpProjProposalTrans
        {
            if (i == 0)
            {
                this.parmDefaultDimension(_tmpProjProposalTrans.DefaultDimension);
                this.setProjProposalJour(_tmpProjProposalTrans.ProjInvoiceProjId, _tmpProjProposalTrans.ProjId,
                _tmpProjProposalTrans.FundingSourceRefId, _tmpProjProposalTrans.CurrencyCode, 
                _tmpProjProposalTrans.TaxInformation_IN, _tmpProjProposalTrans.FixedExchRate);
                i++;
            }

            this.setProjProposalJourPost(_tmpProjProposalTrans);

            select firstonly RecId from projTable
                where projTable.ProjId == _tmpProjProposalTrans.ProjId &&
                        projTable.ProjInvoiceProjId == _tmpProjProposalTrans.ProjInvoiceProjId
                exists join proposalJour
                    where proposalJour.ProjInvoiceProjId == projTable.ProjInvoiceProjId &&
                            proposalJour.ProposalId == projProposalJour.ProposalId;

            if (projTable.RecId)
            {
                this.doEmpl(_tmpProjProposalTrans.ProjInvoiceProjId, _tmpProjProposalTrans.RefRecId);
            }

            if (!this.parmSkipRecalculateProposalTotals())
            {
                this.updateInvoice();

                // When an invoice proposal is generated, the system will
                // automatically create retainage withholding records and/or retainage billing records at the project level.
                this.calcRetention();
            }
        }
    }

    public static ProjProposalId createDataInTmp(ProjFundingSourceRefId _fundingSource, JournalId _journalId)
    {
        ProjInvoiceProposalCreateLinesParams proposalCreateLinesParams = ProjInvoiceProposalCreateLinesParams::construct();
        ProjInvoiceProposalCreateLines proposalCreateLines;
        ProjInvoiceProposalInsertLines projInvoiceProposalInsertLines;
        ProjEmplTransSale ProjEmplTransSale;
        PSATmpProjProposalTrans tmpProjProposalTrans;
        ProjEmplTrans ProjEmplTrans;
        ProjJournalTrans projJournalTrans;
        TransDate invoiceDate = DateTimeUtil::getToday(DateTimeUtil::getUserPreferredTimeZone());
        ProjProposalId proposalId;

        proposalCreateLinesParams.parmInvoiceDate(invoiceDate);
        proposalCreateLinesParams.parmInvoiceTypeSelection(ProjInvoiceTypeSelection::Both);

        proposalCreateLines = ProjInvoiceProposalCreateLines::newStandard(proposalCreateLinesParams.pack());
        proposalCreateLines.parmProposalCreateLinesParams().parmInvoiceDate(invoiceDate);

        projInvoiceProposalInsertLines = new ProjInvoiceProposalInsertLines(proposalCreateLines, false);

        select firstonly Voucher from ProjJournalTrans
            where ProjJournalTrans.JournalId == _journalId;

        while select ProjEmplTrans
            where ProjEmplTrans.VoucherJournal == ProjJournalTrans.Voucher
        {
            select firstonly ProjEmplTransSale
                where ProjEmplTransSale.TransId == ProjEmplTrans.TransId
                   && ProjEmplTransSale.FundingSource == _fundingSource;

            tmpProjProposalTrans.initFromProjEmplTrans(ProjEmplTrans);
            tmpProjProposalTrans.FundingSourceRefId = _fundingSource;
            tmpProjProposalTrans.LineAmount = ProjEmplTransSale.LineAmount * -1;
            tmpProjProposalTrans.SalesPrice = ProjEmplTransSale.SalesPrice;
            tmpProjProposalTrans.Selected = true;
            tmpProjProposalTrans.RefRecId = ProjEmplTransSale.RecId;
            tmpProjProposalTrans.RefTableId = ProjEmplTransSale.TableId;
            tmpProjProposalTrans.IndirectAmount = ProjEmplTransSale.PSAIndirectInvoice;
            tmpProjProposalTrans.insert();
        }

        new TestCreateInvoiceProposal(proposalCreateLines, false).createInvoiceProposals(tmpProjProposalTrans);

        select firstonly ProjEmplTrans
            where ProjEmplTrans.VoucherJournal == ProjJournalTrans.Voucher
        join ProjEmplTransSale
            where ProjEmplTransSale.TransId == ProjEmplTrans.TransId
               && ProjEmplTransSale.FundingSource == _fundingSource;

        proposalId = ProjTrans::newProjEmplTransSale(ProjEmplTrans, ProjEmplTransSale).proposalId();

        return proposalId;
    }
}
    

Main Method to Call the Above Code

Below is the main method to call the above code from a runnable class:


public class TestCreateHourJournalPostAndCreateInvProposalAndPostInvoice
{
    public static void main(Args _args)
    {
        JournalId journalId;
        ProjProposalId 	proposalId;
        InvoiceId 	invoiceId;
        
        TestCreateHourJournalPostAndCreateInvProposalAndPostInvoice objCreateHourJourAndCreateAndPostInvoice = new TestCreateHourJournalPostAndCreateInvProposalAndPostInvoice();

        journalId = objCreateHourJourAndCreateAndPostInvoice.createJournal();
        objCreateHourJourAndCreateAndPostInvoice.postJournal(journalId);
        proposalId = objCreateHourJourAndCreateAndPostInvoice.invoiceProposalCreation(journalId);
        invoiceId = objCreateHourJourAndCreateAndPostInvoice.postInvoiceProposal(proposalId);
        info(strfmt("invoice %1 posted",invoiceId);
    }
}
    

Wednesday, April 2, 2025

How to set background color for alternate row in SSRS report table D365FO

Setting Background Color for Alternate Rows in SSRS Report Table

Setting Background Color for Alternate Rows in SSRS Report Table

In this blog post, we will explore how to set the background color for alternate rows in an SSRS report table in Dynamics 365 Finance and Operations. This can be achieved using two different expressions:

1. Based on Unique Value in Table

To set the background color based on a unique value in the table, you can use the following expression:

=IIF(RUNNINGVALUE(Fields!FieldName.Value, CountDistinct, Nothing) Mod 2, "#ffffff", "#f0f0f0")

2. Based on Row Number

To set the background color based on the row number, you can use the following expression:

=IIF(RowNumber(Nothing) Mod 2, "#ffffff", "#f0f0f0")

Implementation

To implement these expressions in your SSRS report:

  1. Open your report in design in visual studio.
  2. Select the table where you want to apply the alternate row background color.
  3. Right-click on the data row (after table header) row and select Properties.
  4. In the BackgroundColor property, enter one of the expressions mentioned above.
  5. Click OK to apply the changes.

By following these steps, you can easily set the background color for alternate rows in your SSRS report table.

Please note - if you do the grouping on table then these background colors will also not show properly.

How to access extension class method on data entity field property

How to Use Extension Class Methods on Field Properties of Data Entity

How to Use Extension Class Methods on Field Properties of Data Entity

If you want to use your extension class method on your field property, then please use the following syntax:

extension class name :: method name

Example:

TestCustCustomerV3Entity_Extension::testCollectionAgent

Note: Brackets are not required.

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.

Wednesday, January 22, 2025

Get Description of financial dimension value from SQL DB of D365FO

Retrieve Financial Dimension Value Description using SQL Query in D365FO

How to Retrieve Financial Dimension Value Description with SQL in D365FO

In this post, we will explore how to retrieve the description of a financial dimension value in an ERP or financial management system using a specific SQL query in D365FO.

The Query


SELECT DIMATTVALUE.DISPLAYVALUE AS 'DIMENSIONVALUE',
       DIMATT.NAME AS 'FINANCIALDIMENSION',
       DIMATTVALUE.ISSUSPENDED,
       HCMWORKER.PERSONNELNUMBER AS 'OWNER',
       DimFinTag.DESCRIPTION
FROM DIMENSIONATTRIBUTEVALUE AS DIMATTVALUE
LEFT JOIN HCMWORKER ON DIMATTVALUE.OWNER = HCMWORKER.RECID
JOIN DIMENSIONATTRIBUTE AS DIMATT ON DIMATT.RECID = DIMATTVALUE.DIMENSIONATTRIBUTE
JOIN DIMENSIONATTRIBUTEDIRCATEGORY dimAttDirCategory 
ON dimAttDirCategory.DIMENSIONATTRIBUTE = DIMATTVALUE.DIMENSIONATTRIBUTE
JOIN DIMENSIONFINANCIALTAG DimFinTag ON DIMATTVALUE.DISPLAYVALUE = DimFinTag.VALUE 
AND DimFinTag.FINANCIALTAGCATEGORY = dimAttDirCategory.DIRCATEGORY
WHERE DIMATTVALUE.ISDELETED = 0
  AND DIMATTVALUE.ISSUSPENDED = 0
        

Feel free to leave a comment below if you have any questions or need further clarification on the query or its results!

Monday, January 13, 2025

Call custom service with JSON body in D365FO

Call Custom Service with JSON Body in D365FO

Call Custom Service with JSON Body in D365FO

        
FileUploadTemporaryStorageResult fileUpload = File::GetFileFromUser() as FileUploadTemporaryStorageResult;
System.IO.Stream stream = fileUpload.openResult();
str responceJson;

using (var reader = new System.IO.StreamReader(stream))
{
    responceJson = reader.ReadToEnd();

    TESTPOReceiptRequestContract contract = FormJsonSerializer::deserializeObject(
        classnum(TESTPOReceiptRequestContract), responceJson);

    new TESTPOReceiptService().processRecord(contract);
}
        
    

How to Create Multiselection Lookup in D365FO

How to Create Multiselection Lookup in D365FO How to Create Multiselection Lookup in D365FO In this blog p...