Author: tw

  • Advanced bank reconciliation – A practical view

    Advanced bank reconciliation – A practical view

    Documentation https://learn.microsoft.com/en-us/dynamics365/finance/accounts-payable/import-bai2-er

    UBS Camt.053 Implentation guidelines Dokumente Zahlungsverkehr | UBS Schweiz

    Functional overview

    Advanced bank reconciliation provides the following functionality:

    • Import Camt.053 Bank statement (I could not get MT940 to work and did not test BAI2)
    • Match statement to existing bank transactions based on customizable matching rules
    • Create new bank transaction based on customizable matching rules
      • The offset of bank transactions is Ledger only (no customer or vendor transaction)
      • The offset account selection is linked to the statement transaction codes provided by the bank

    Process

    Overview

    • IMPORTANT: Do not import a bank statement before the previous bank statement is reconciled and posted.
    • Import (Camt.053) Bank statement
    • Create Reconciliation (Manual or automatic)
    • Run matching rules (Manual or automatic)
    • Match remaining transaction and check automatically matched transactions
    • Mark as reconciled
    • Return to statement to post the new transactions

    Bank account

    Links to “Bank statements” (for Bank statement import and posting) and “Bank reconciliation” (for reconciliation)

    Import the bank statement

    IMPORTANT: Do not import a bank statement before the previous bank statement is reconciled and posted.

    Bank account > Reconcile > Bank Statements

    Create Reconciliation

    Bank account > Reconcile > Bank statements

    Depending on the setup the statement was created automatically during import of the statement or you can create it manually here

    Reconciliation Worksheet

    • Check result
    • Match manually if needed
    • When done, “Mark as reconciled”
    • Note: If Transactions are missing, they might be in future dates. In this case go back to the Bank reconciliation overview and change the Cut-off date. After the matching is completed, return the date back to the Statement date.

    Post statement

    • Go back to bank statement
    • Check Accounting date – Must be Bank Statement Date (this posting of the new transactions will occur on this date)
    • Post

    Customizations

    The above processes did not work without some minor customization

    Customization by Electronic reporting definition

    ISO.053 without IBAN as Identifier

    Issue: the HSBC UK CAMT.053 file provideds <Routing number> + <Account number> in the <Acct>.<Id>.<Othr> field; the standard implementation expects only <Account number> in the field. This caused the error “The bank statement %1 was not imported because bank account %2 was not identified in the import file.”

    Solution: Use electronic reporting to trim the account number:
    Designer for Model mapping “ABR Camt.053 format mapping”  (NOT format)

    Differentiate “OTHR” Payments

    Issue: Direct debits are all identified as “OTHR” Payments but need to be posted in different ways. The standard only allows differentiations by Statement transaction code which ist defined by the bank.

    Solution: Using Electronic reporting add the first three characters of the EndToEndId to the Statement transaction code enabling further differentiation of the OTHR payments

    Designer for Model mapping “ABR Camt.053 format mapping”  (NOT format); format/Document/BkToCstmrStmt/Stmt/Ntry/NtryDtls/TxDtls

    CASE(@.BkTxCd.IsMatched,
    True, IF(  @.BkTxCd.Data.Domn.IsMatched,
    CONCATENATE(
    @.BkTxCd.Data.Domn.Data.Cd.Str,
    @.BkTxCd.Data.Domn.Data.Fmly.Cd.Str,
    @.BkTxCd.Data.Domn.Data.Fmly.SubFmlyCd.Str,
    IF(TEXT(@.BkTxCd.Data.Domn.Data.Fmly.SubFmlyCd.Str) = "OTHR", LEFT(@.Refs.Data.EndToEndId.Data.Str, 3), "")),
    IF(@.BkTxCd.Data.Prtry.IsMatched,
    @.BkTxCd.Data.Prtry.Data.Cd.Str,"")
    ),
    IF(format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Domn.IsMatched,
    CONCATENATE(
    format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Domn.Data.Cd.Str,
    format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Domn.Data.Fmly.Cd.Str,
    format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Domn.Data.Fmly.SubFmlyCd.Str,
    IF(TEXT(format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Domn.Data.Fmly.SubFmlyCd.Str) = "OTHR", LEFT(@.Refs.Data.EndToEndId.Data.Str, 3), "")),
    IF(format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Prtry.IsMatched,
    format.Document.BkToCstmrStmt.Stmt.Ntry.BkTxCd.Prtry.Data.Cd.Str,""))
    )

    Import field “AddtlNtryInf” from statement to “Description” (AdditionalEntryInfo) field

    Issue: The “Description” field (AdditionalEntryInformation) is populate by the “EndToEndId” which contains too little information in the imported Bank statement. The Bank statement file contains more information in the “AddtlNtryInf” field.

    Solution: Using Electronic reporting

    • Change the mapping of EndToEndId: Replace with AddtlNtryInf if available (this will map to BankStmtISOReportEntry.AdditionalEntryInformation)
    • Change the mapping of BankDocumentNumber: Add the original EndToEndId value here (this will map the value to BankStmtISOReportEntry.BankDocumentNumber)

    Customization in code

    Allow change of posting date

    Issue: The standard always posts new statement transaction to today (AccountingDate is set to today() during initialization)

    Solution: In Code add new edit method to allow change of AccountingDate of BankStmtISOAccountStatement

    Allow change of cutoff date

    Issue: Allow change of cutoff date, to include transactions that were posted after the bank statement. This is actually possible in standard, however after matching the change should be reversed to link the reconciliations to the correct statement.

    Solution: In Code disable validation of CutOffDate changes on BankReconciliationHeader and create edit method to allow change even after reconciliation.

  • Extend Map CustVendTrans

    Extend Map CustVendTrans

    Scenario

    New field MyNewField as NoYes on CustTrans and VendTrans

    Simple solution

    Obejcts

    Create method parmMyNewField on CustVendTans, CustTrans and VendTrans using Table code extensions.

    Usage

    custVendTrans.parmMyNewField(NoYes::Yes) will work fine if the record hast been inserted.

    Issue

    If custVendTrans has not yet been inserted a call to custVendTrans.parmMyFlag(NoYes::Yes) will result in the errors
    Error executing code: CustTrans table does not have method ‘parmMyNewField’.
    or
    Error executing code: VendTrans table does not have method ‘parmMyNewField’.

    Full Solution

    Extend the CustVendTransInterface class to properly handle mapped functions irrespective of the record being created or not.

    Objects

    • Create class MyCustVendTransInterface following plugin design pattern, decorated with [… ExportInterfaceAttribute]
      • Create sub-class MyCustTransCustVendTrans
        decorated with [… ExportMetadataAttribute(…CustTrans…), … ExportAttribute(…MyCustVendTransInterface…)]
      • Create sub-class MyVendTransCustVendTrans
        decorated with [… ExportMetadataAttribute(…VendTrans…), … ExportAttribute(…MyCustVendTransInterface…)]
    [Microsoft.Dynamics.AX.Platform.Extensibility.ExportInterfaceAttribute]
    public abstract class MyCustVendTransInterface
    {
        CustVendTransInterface custVendTransInterface;
    
        private void initializeCustVendTransInterface(CustVendTransInterface _custVendTransInterface)
        {
            custVendTransInterface = _custVendTransInterface;
        }
    
        public CustVendTrans parmCustVendTrans()
        {
            return custVendTransInterface.parmCustVendTrans();
        }
    
        protected void new()
        {
        }
    
        public static MyCustVendTransInterface createInstance(CustVendTransInterface _custVendTransInterface)
        {
            SysPluginMetadataCollection metadataCollection = new SysPluginMetadataCollection();
    
            metadataCollection.SetManagedValue(classStr(MyCustVendTransInterface), tableId2Name(_custVendTransInterface.parmCustVendTrans().tableId));
            MyCustVendTransInterface instance = SysPluginFactory::Instance(identifierStr(Dynamics.AX.Application), classStr(MyCustVendTransInterface), metadataCollection);
    
            instance.initializeCustVendTransInterface(_custVendTransInterface);
    
            return instance;
        }
    
        public NoYes parmMyNewField(NoYes _myNewField= NoYes::No)
        {
            return _myNewField;
        }
    }
    
    [System.ComponentModel.Composition.ExportMetadataAttribute(classStr(MyCustVendTransInterface), tableStr(CustTrans))
        ,System.ComponentModel.Composition.ExportAttribute('Dynamics.AX.Application.MyCustVendTransInterface')]
    public class MyCustTransCustVendTrans extends MyCustVendTransInterface
    {
        private CustTrans parmCustTrans()
        {
            return this.parmCustVendTrans();
        }
    
        public NoYes parmMyNewField(NoYes _myNewField = this.parmCustTrans().MyNewField)
        {
            CustTrans   custTrans = this.parmCustTrans();
            custTrans.MyNewField = _myNewField;
            return custTrans.MyNewField;
        }
    }
    
    [System.ComponentModel.Composition.ExportMetadataAttribute(classStr(MyCustVendTransInterface), tableStr(VendTrans))
        ,System.ComponentModel.Composition.ExportAttribute('Dynamics.AX.Application.MyCustVendTransInterface')]
    public class MyVendTransCustVendTrans extends MyCustVendTransInterface
    {
        private VendTrans parmVendTrans()
        {
            return this.parmCustVendTrans();
        }
    
        public NoYes parmMyNewField(NoYes _myNewField = this.parmVendTrans().MyNewField)
        {
            VendTrans   vendTrans = this.parmVendTrans();
            vendTrans.MyNewField = _myNewField;
            return vendTrans.MyNewField;
        }
    }
    
    • Optional: create extenstion class CustVendTransInterface_My_Extension
      to provide simple access to the newly created class MyCustVendTransInterface
    [ExtensionOf(classStr(CustVendTransInterface))]
    final class MyCustVendTransInterfaceCls_Extension
    {
        private MyCustVendTransInterface MyCustVendTransInterface;
    
        public MyCustVendTransInterface MyCustVendTransInterface()
        {
            if (!MyCustVendTransInterface)
            {
                MyCustVendTransInterface = MyCustVendTransInterface::createInstance(this);
            }
    
            return MyCustVendTransInterface;
        }
    }
    

    Usage

    //if extension CustVendTransInterface_My_Extension implemented
    MyCustVendTransInterface myCustVendTransInterface = CustVendTransInterface::createInstance(custVendTrans).myCustVendTransInterface()
    //if extension CustVendTransInterface_My_Extension not implemented
    MyCustVendTransInterface myCustVendTransInterface = MyCustVendTransInterface::createInstance(CustVendTransInterface::createInstance(custVendTrans))
    //access field
    myCustVendTransInterface.parmMyNewField(NoYes::Yes);
    

    Final notes

    Microsoft documents the extension of maps here:
    Extend table maps that are used as interfaces
    However the example SalesPurchTable they are using has a special attribute class to decorate the methods (SalesPurchTableInterfaceFactoryAttribute. The example here is both more generic and simpler.

  • Functions to use in Queries, Filters (2021)

    Functions to use in Queries, Filters (2021)

    Update to my old Blog post Functions to use in Queries, Filters

    There are many new functions that can be used in Queries. In the code they are marked with [QueryRangeFunction()] or [QueryRangeFunctionAttribute()]

    see https://calafell.me/the-utility-of-query-range-functions-dynamics-365-for-finance-operations-supply-chain-management/ for a comprehensive list and a script to compile a new list your current list.

    Use by providing the full class and method e.g. UserExternalQueryRangeFunction::currentUserExternalParty()

    Current list as of 10.0.22

    • str SysQueryRangeUtilDMF::greaterThanLastActivityStartedDateTime()
    • str SysQueryRangeUtilDMF::greaterThanLastActivityCompletedDateTime()
    • str SysQueryRangeUtilDMF::greaterThanLastSuccessfulExecutionDateTime()
    • str SysQueryRangeUtilDMF::greaterThanLastExecutionDateTime()
    • str SysQueryRangeUtilDMF::notExportableUser()
    • RecId UserExternalQueryRangeFunction::currentUserExternalParty()
    • date SysQueryRangeUtil::currentDate()
    • str SysQueryRangeUtil::currentLanguageId()
    • str SysQueryRangeUtil::currentSessionDate()
    • str SysQueryRangeUtil::currentSessionDateTime()
    • str SysQueryRangeUtil::currentUserId()
    • str SysQueryRangeUtil::anyUserIdNotCurrent()
    • str SysQueryRangeUtil::currentUserLanguage()
    • str SysQueryRangeUtil::dateRange(date _startDate, date _endDate)
    • str SysQueryRangeUtil::day(int relativeDays = 0)
    • str SysQueryRangeUtil::dayRange(int relativeDaysFrom = 0, int relativeDaysTo = 0)
    • str SysQueryRangeUtil::greaterThanDate(int relativeDays = 0)
    • str SysQueryRangeUtil::greaterThanUtcDate(int relativeDays = 0)
    • str SysQueryRangeUtil::greaterThanUtcNow()
    • str SysQueryRangeUtil::lessThanDate(int relativeDays = 0)
    • str SysQueryRangeUtil::lessThanUtcDate(int relativeDays = 0)
    • str SysQueryRangeUtil::lessThanUtcNow()
    • str SysQueryRangeUtil::monthRange(int relativeMonthsFrom = 0, int relativeMonthsTo = 0)
    • str SysQueryRangeUtil::yearRange(int relativeYearsFrom = 0, int relativeYearsTo = 0)
    • str SysQueryRangeUtil::value(anytype a)
    • str SysQueryRangeUtil::valueEmptyString()
    • str SysQueryRangeUtil::valueUnlimited()
    • SelectableDataArea SysQueryRangeUtil::currentCompany()
    • utcdatetime SysQueryRangeUtil::getCurrentSessionDateTime()
    • str SysQueryRangeUtil::literalFilterValue(str nonLiteralFilterValue)
    • utcdatetime SysQueryRangeUtil::dateTimeMinValue()
    • str AgreementClassificationEntityHelper::salesAgreementClassificationRangeConfiguration()
    • str AgreementClassificationEntityHelper::purchAgreementClassificationRangeConfiguration()
    • str AgreementClassificationEntityHelper::agreementClassificationRangeConfiguration()
    • str AgreementConfirmationLineEntityHelper::assignLinesToHeadersByDateEffectiveness_ SalesAgreementConfirmation()
    • str AgreementConfirmationLineEntityHelper::assignLinesToHeadersByDateEffectiveness_ PurchaseAgreementConfirmation()
    • RefRecId CompanyInfoHelper::currentLegalEntity()
    • str CustCustomerExternalCodeClassEntityHelper::queryRangeExpressionCustTableTableId()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeConfiguration()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeSize()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeColor()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeStyle()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeVersion()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeSite()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeWarehouse()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeLocation()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeStatus()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeLicensePlate()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeBatch()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeSerial()
    • str EcoResDimensionGroupEntityHelper::productDimensionRangeInventoryOwner()
    • str EcoResDimensionGroupEntityHelper_RU::productDimensionRangeInventoryProfile()
    • str EcoResDimensionGroupEntityHelper_RU::productDimensionRangeGTD()
    • str EcoResProductDimensionQueryRangeHelper::productDimensionRangeColor(DataSourceName _dataSource1Name, DataSourceName _d
    • ataSource2Name, boolean _allowEmptyDimension = true)
    • str EcoResProductDimensionQueryRangeHelper::productDimensionRangeSize(DataSourceName _dataSource1Name, DataSourceName _da
    • taSource2Name, boolean _allowEmptyDimension = true)
    • str EcoResProductDimensionQueryRangeHelper::productDimensionRangeStyle(DataSourceName _dataSource1Name, DataSourceName _d
    • ataSource2Name, boolean _allowEmptyDimension = true)
    • str EcoResProductDimensionQueryRangeHelper::productDimensionRangeConfiguration(DataSourceName _dataSource1Name, DataSourc
    • eName _dataSource2Name, boolean _allowEmptyDimension = true)
    • str EcoResProductDimensionQueryRangeHelper::productDimensionRangeVersion(DataSourceName _dataSource1Name, DataSourceName
    • _dataSource2Name, boolean _allowEmptyDimension = true)
    • str EcoResProductEntityHelper::queryRangeExpressionNotVariant()
    • str EcoResProductMasterDimensionEntityHelper::productDimensionColorTableId()
    • str EcoResProductMasterDimensionEntityHelper::productDimensionConfigurationTableId()
    • str EcoResProductMasterDimensionEntityHelper::productDimensionSizeTableId()
    • str EcoResProductMasterDimensionEntityHelper::productDimensionStyleTableId()
    • str EcoResProductMasterDimensionEntityHelper::productDimensionVersionTableId()
    • str EcoResReleasedProductExternalCodeClassEntityHelper::queryRangeExpressionInventTableTableId()
    • str EcoResReleasedProductVariantExternalCodeClassEntityHelper::queryRangeExpressionInventDimCombinationTableId()
    • str FormLetterSortingParametersEntityHelper::procurementTableRangeValues()
    • str FormLetterSortingParametersEntityHelper::salesTableRangeValues()
    • str IntrastatEntityHelper::intrastatCommodityCodeEntityRangeDataAreaId()
    • str InventInventoryDimensionsParametersEntityHelper::tableNumInventDim()
    • str InventInventoryDimensionsParametersEntityHelper::includedFieldIds()
    • str InventLedgerPostingDefinitionEntityHelper::inventAccountTypeInventFilter()
    • str InventLedgerPostingDefinitionEntityHelper::inventAccountTypeProdFilter()
    • str InventLedgerPostingDefinitionEntityHelper::inventAccountTypePurchFilter()
    • str InventLedgerPostingDefinitionEntityHelper::inventAccountTypeSalesFilter()
    • str InventLedgerPostingDefinitionEntityHelper::inventAccountTypeStdCostVarianceFilter()
    • FieldId InventSysQueryRangeUtil::inventBatchIdFieldNum()
    • FieldId InventSysQueryRangeUtil::wMSLocationIdFieldNum()
    • str PmfSysQueryRun::pmfInventTableIsBOMFormula()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenScheduledAndStartedUp()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenScheduledAndReportedFinished()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenCostEstimatedAndStartedUp()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenStartedUpAndReportedFinished()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenCostEstimatedAndReportedFinished()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenCreatedAndStartedUp()
    • str ProdStatusSysQueryRangeUtil::prodStatusBetweenCreatedAndReportedFinished()
    • str PurchaseRequisitionEntityHelper::getPurchaseRequsitionHeaders()
    • str PurchPurchaseOrderUpdateLinesParametersEntityHelper::tableNumPurchTable()
    • str PurchPurchaseOrderUpdateLinesParametersEntityHelper::includedFieldIds()
    • str PurchPurchasingPolicyEntityHelper::purchasingPolicyType()
    • str PurchPurchasingPolicyEntityHelper::purchReqLineFilterQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequisitionProcurementCatalogPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequisitionConsumptionCategoryAccessPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseCategoryPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequistionControlRFQPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequistionConsumptionControlPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequistionPurposePolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequistionReplenishmentCategoryAccessPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseRequistionReplenishmentControlPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchaseOrderCreationAndDemandConsolidationPolicyRuleQueryRangeValue()
    • str PurchPurchasingPolicyEntityHelper::purchReapprovalPolicyRuleQueryRangeValue()
    • str ReqDemPlanHistoricalExternalDemandEntityHelper::queryRangeVersionForecastedExternally()
    • str ReqDemPlanHistoricalExternalDemandEntityHelper::queryRangeDataSourceOriginForecastedExternally()
    • str ReqRoutePlannedOrderRouteOperationEntityHelper::wrkCtrDateFromEffectiveness()
    • str ReqRoutePlannedOrderRouteOperationEntityHelper::wrkCtrDateToEffectiveness()
    • str RetailPeriodicDiscountQueriesHelper::getDiscountOfferTypes()
    • str RetailPeriodicDiscountQueriesHelper::getPriceOfferTypes()
    • str RetailProductWarehouseInventoryQueryHelper::getReservationHierarchyFieldIds()
    • str SalesOrderUpdateLinesParametersEntityHelper::tableNumSalesTable()
    • str SalesOrderUpdateLinesParametersEntityHelper::includedFieldIds()
    • str SalesQueryRangeUtil::salesStatusCanBeInvoiced()
    • str SalesQueryRangeUtil::salesTypeCustomerDemand()
    • str SysQueryRangeUtilProject::projActiveAll(ProjActiveAll _projActiveAll)
    • str SysQueryRangeUtilProject::projLevels(ProjLevels _projLevels)
    • str SysQueryRangeUtil_Extension::validToMaxValueRange()
    • str SysQueryRangeUtil_Extension::validToMaxValueRangeDate()
    • str TaxCodeExternalCodeClassEntityHelper::queryRangeExpressionTaxTableTableId()
    • str TMSTransportationRouteScheduleEntityHelper::getRoutePlan()
    • str TrvExpenseTransHelper::currentLegalEntity()
    • str TSQueryRangeUtil::validTimesheetFrequency()
    • TableId TSTimesheetHelper::getTSTimesheetTableId()
    • str VendVendorExternalCodeClassEntityHelper::queryRangeExpressionVendTableTableId()
    • str WHSCustomerGroupProductFilterEntityHelper::getCustomerGroups()
    • str WHSCustomerProductFilterEntityHelper::getCustomers()
    • str WHSGenerallyAvailableProductFilterEntityHelper::getGenerallyAvailableProducts()
    • str WHSProductFilterGroupDetailEntityHelper::getProductFilterGroupsQueryRange()
    • str WHSQueryHelper_BR::getRefTableIdsFromWhsFiscalDocument()
    • str WHSReleaseLoadToWarehouseSysQueryRangeUtil::loadStatus()
    • str WHSSysQueryRangeUtil::workStatusOpenOrInProcess()
    • str WHSSysQueryRangeUtil::no()
    • str WHSVendorGroupProductFilterEntityHelper::getVendorGroups()
    • str WHSVendorProductFilterEntityHelper::getVendors()
    • str WMSQueryRangeUtil::shipmentsInProgress()
    • TableId FinancialReportingGeneralJournalReversals::tableName2Id(str _tableName)
    • str AssetWorkspaceQueryHelper::currentFiscalYearRange()
    • str BudgetPlanningWorkspaceQueryRangeHelper::budgetPlanningProcess()
    • str BudgetPlanningWorkspaceQueryRangeHelper::currentDate()
    • str BudgetPlanningWorkspaceQueryRangeHelper::emptyValue()
    • str BudgetTrackingWorkspaceQueryRangeHelper::overThreshold()
    • str BudgetTrackingWorkspaceQueryRangeHelper::underThreshold()
    • str BudgetTrackingWorkspaceQueryRangeHelper::budgetPlanningProcess()
    • str BudgetTrackingWorkspaceQueryRangeHelper::currentLedger()
    • str DirRelationshipTypeHelper::getUserDefinedRelationshipTypeRange(DataSourceName _dataSourceName = ”, FieldName _system
    • TypeFieldName = fieldStr(DirRelationshipTypeTable, SystemType)
    • boolean DirUtility::currentRoleHasAccessToPrivate(boolean _useCache = true)
    • DirPartyRecId DirUtility::currentParty()
    • boolean DirPartyLocation::currentRoleHasAccessToPrivate(boolean _useCache = true)
    • DirPartyRecId DirPartyTable::currentParty()
    • str DocQueryRangeUtil::isRunningInBatch()
    • str DocQueryRangeUtil::isNotRunningInBatch()
    • str DOMConfigurationManager::notUtcNull()
    • str DOMConfigurationManager::domBatchTimeGreaterThanBatchTime()
    • str DOMConfigurationManager::getDOMClassId()
    • str DOMQueryHelper::getQueryNotValueForExcludedModesOfDelivery()
    • str EngChgTools::productDimensionRangeVersion()
    • str EntAssetSysQueryRangeUtil::greaterThanUtcDateCompanyTimezone(int relativeDays = 0)
    • RecId HcmWorkerLookup::currentPerson()
    • HcmWorkerRecId HcmWorkerLookup::currentWorker()
    • boolean EssPersonIdentificationNumberRangeUtil::skipSelfServiceReadPolicy()
    • boolean EssPersonIdentificationNumberRangeUtil::skipSelfServiceInsertUpdateDeletePolicy()
    • RefRecId HcmCompanyInfoHelper::currentLegalEntity()
    • boolean HcmPersonIdentificationNumberRangeUtil::skipSelfServiceReadPolicy()
    • boolean HcmPersonIdentificationNumberRangeUtil::skipSelfServiceInsertUpdateDeletePolicy()
    • str HcmQueryRangeUtil::getAotNameHcmHumanResourceManager()
    • str HcmQueryRangeUtil::getAotNameHcmManager()
    • HcmPositionHierarchyTypeRecId HcmQueryRangeUtil::findPositionHierarchyLineType()
    • str HcmQueryRangeUtil::lessThanCompanyDate(int _relativeDays = 0)
    • str HcmQueryRangeUtil::greaterThanCompanyDate(int _relativeDays = 0)
    • str HcmQueryRangeUtil::dateTimeRangeTodayToEndOfYearInCompanyTimezone()
    • str HcmQueryRangeUtil::currentWorkerPositionRange()
    • str HcmQueryRangeUtil::greaterThanOrEqualToUtcNow()
    • str HcmQueryRangeUtil::lessThanOrEqualToUtcNow()
    • boolean HcmWorkerActionHireRangeUtil::skipSelfServiceReadPolicy()
    • boolean HcmWorkerActionHireRangeUtil::skipSelfServiceInsertUpdateDeletePolicy()
    • boolean HcmWorkerBankAccountRangeUtil::skipSelfServiceReadPolicy()
    • boolean HcmWorkerBankAccountRangeUtil::skipSelfServiceInsertUpdateDeletePolicy()
    • str MssExitingWorkerQueryRangeUtil::dateTimeRangeExiting()
    • str MssExitingWorkerQueryRangeUtil::numberOfHierarchyResultLevelRange()
    • str HcmBenefitEligibilityPolicyEntity::hcmBenefitEligibilityPolicyType()
    • str SysPolicyEntityHelper::sysPolicyRuleTypeTableId()
    $rootDirectory = "C:\AOSService\PackagesLocalDirectory"
    
    cd $rootDirectory
    ls "*.xml" -Recurse | % {
        $matches = (gc $_.FullName -ReadCount 0 | Select-String '\[QueryRangeFunction(?:Attribute)?\(?\)?\]\s+?public static ([^\r\n ]+) ([^\r\n)]+)' -AllMatches).Matches
    
        if ($matches.Count -ne 0)
        {
            $file = $_.Name.Substring(0, $_.Name.Length -4)
            #$fullName = $_.FullName
    
            foreach ($m in $matches) {
                Write-Host "$($m.Groups[1]) " -NoNewline
                Write-Host "$($file)." -NoNewline -ForegroundColor Gray
                Write-Host "$($m.Groups[2]))"
            }
        }
    }
    
  • Electronic Reporting on DEVBox

    Electronic Reporting on DEVBox

    If you do not have a connection to LCS on your DEVBox you can still access the new Microsoft definitions by doing the following:

    Download the definitions from LCS

    https://lcs.dynamics.com/V2/SharedAssetLibrary

    “DOWNLOAD ALL” in LCS

    Extract in DEVBox

    C:\Temp\SolutionRepository

    Extract LCS ZipFile to C:\Temp\SolutionRepository

    Add File System Repository

    https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=USMF&mi=ERWorkspace

    Edit the Microsoft Repositories
    Add a “File system” repository. NOTE: This requires the debugger – see next step.

    Debugger required

    Set breakpoint in ERSolutionImportRepository::getTypes() and use the debugger to add the ERSolutionFileSystemRepository type.

    Import local definitions

    Open the newly added file system repository
    Import the desired definitions – or import all if you like.
  • Find Datatypes (updated)

    Find Datatypes (updated)

    Update of my old script to D365

    class FindEDT
    {
        public static void main(Args _args)
        {
            #AOT
            Dialog              dialog = new Dialog('Find EDT');
            str                 searchString;
            DialogField         dialogField = dialog.addField(extendedTypeStr(MCRSearchText), 'Search string');
            boolean             stopAfterFound;            
            ;
            if(dialog.run())
            {
                searchString = dialogField.value();
                if(!strScan(searchString, "*", 1, 999)) //no "*"
                {
                    searchString = "*" + searchString + "*";
                }
                else if(strKeep(searchString,"*") == "*" && subStr(searchString,strLen(searchString),1)=="*") //just one final "*": beginns with
                {
                    stopAfterFound = true;
                }
                info(strfmt("Find %1",searchString));
                if(searchString)
                {
                    setPrefix('Extended Data Types');
                    FindEDT::searchNodes(#ExtendedDataTypesPath, searchString, stopAfterFound);
                    setPrefix('Base Enums');
                    FindEDT::searchNodes(#BaseEnumsPath, searchString, stopAfterFound);
                }
            }
        }
        static void searchNodes(str _basePath, str _searchString, boolean _firstonly)
        {
            #AOT
            TreeNode            treeNode;
            TreeNodeIterator    treeNodeIterator;
            SysDictType         sysDictType;
            SysDictEnum         sysDictEnum;
            Boolean             found;
            ;
            treeNode = TreeNode::findNode(_basePath);
            treeNodeIterator = treenode.AOTiterator();
            treeNode = treeNode.AOTfirstChild();
            treeNode = treeNodeIterator.next();
            while(treeNode)
            {
                if(treeNode.treeNodeName() like "*" + _searchString + "*")
                {
                    if(_basePath == #ExtendedDataTypesPath)
                    {
                        sysDictType = sysDictType::newTreeNode(treeNode);
                    }
                    if(sysDictType)
                    {
                        if (sysDictType.extend() && sysDictType.enumId())
                        {
                            info(strfmt("%1 (%2 - %3 -> %4)",treeNode.treeNodeName(), sysDictType.baseType()
                                        , extendedTypeId2name(sysDictType.extend()), enumId2Name(sysDictType.enumId()) ));
     
                        }
                        if(sysDictType.extend())
                        {
                            info(strfmt("%1 (%2 - %3)",treeNode.treeNodeName(), sysDictType.baseType()
                                        , extendedTypeId2name(sysDictType.extend()) ));
     
                        }
                        else if (sysDictType.enumId())
                        {
                            info(strfmt("%1 (%2 - %3)",treeNode.treeNodeName(), sysDictType.baseType()
                                        , enumId2Name(sysDictType.enumId()) ));
     
                        }
                        else
                        {
                            info(strfmt("%1 (%2)",treeNode.treeNodeName(), sysDictType.baseType()));
                        }
                    }
                    else
                    {
                        info(strfmt("%1",treeNode.treeNodeName()));
                    }
                    found = true;
                }
                else if(_firstOnly && found)
                {
                    return;
                }
                treeNode = treeNodeIterator.next();
            }
        }
    }
    

    Run with the following link:

    https://usnconeboxax1aos.cloud.onebox.dynamics.com/?cmp=usmf&mi=SysClassRunner&cls=FindEDT

  • Service items vs. Not stocked Products vs. Charges

    Service items vs. Not stocked Products vs. Charges

    In AX 2012 the Item model group property Stocked product was introduced.

    The not stocked products (i.e. products with Stocked product = No) lie somewhere between Service items (i.e. products with Product type = Service) and Charges, without providing a clear distinction between types. Here a comparison with the primary disadvantages highlighted:

    Service itemNot stocked
    product
    Charge
    Product typeServiceItem or Servicen/a
    Item model group – Stocked ProductYesNon/a
    Add to BOM/productionYesNoNo
    Inventory transactionsYesNoNo
    Inventory on handNoNoNo
    Automatic addition to orderNoNoYes
    Integrate in Product Release Workflows / PLMYesYesNo
    Visibility during order entryNormalNormalReduced
    Texts printed on reportsNormalNormalReduced
    SO PickingOptionalNoNo
    SO PackingOptionalIncluded in quantity setting “Picked quantity and not stocked product”No
    SO InvoiceNo quantity setting to include service items that have not been picked or packedNo quantity setting to include service items that have not been picked or packedAutomatic
    Advanced WarehousingNoNoNo
    Consequences
    Why does it exist?For Services and item without stock levels (or with uncounted stock levels e.g. screws, water). They can be used like any product, however their use has no impact on stock levels and vice versa.Unsure why this was introduced instead of fixing the disadvantages of the charges and service itemsFor Freight, Insurance, Packaging
    Question: Find all uses of a productGo to inventory transactions directly on itemNo inquiry available directly from itemNo direct inquiry available
    Question: Find all non-stock/service productsDirectly visible in released product listMore or less visible depending on the naming of the item model group – for a good overview add it to the grid as personalisationSeparate parameter forms
    Unplanned use in BOM e.g. as proper component in manufacturing or as dummy component is setsNo problemNot possibleNot possible
    Change to normal stocked itemNot possibleNot possibleNot possible
    Recommendations
    Scenario 1: Probably as MS intended, less flexibility, no mod desiredUse service items only for items to be used in BOMs; do not allow sale of service items.Use not stocked products for all sellable services.
    Note: Reporting cannot be based on InventTrans.
    Can be replaced by not stocked products, however note that some standard features expect the use of charges e.g. e-Invoice (ITA) expects the Bollo to be a charge. ==> keep using charges for items that clearly have a charge character.
    Scenario 2: More flexibility (Service items sold and used in BOMs. Separate items not desired.)Only use service items.Do not use not stocked productsUse as intended.
    Comparison of Service items, Not stocked Products and Charges

    Comments on the primary disadvantages

    Service item – Picking and Packing requirements

    Not stocked products are considered int the Quantity setting “Picked quantity and not stocked product”, i.e. they do not have to be picking. Service items do not have the corresponding setting, i.e. some way hast to be found to get the service items on the invoice

    • Option 1: Workaround that includes the service items in picking.
    • Option 2: Implement a modification to provide support for service items when in the Quantity setting “Picked quantity and not stocked product”

    Not stocked product – no inventory transaction, can’t be added to BOM

    As there are no inventory transactions the not stocked products cannot be added to a BOM, i.e. they cannot be used for floor stock or outside production service items. Reports based on (or inner-joining e.g. to get costs) InventTrans will not include these items.

    Charges – reduced visibility during sales order entry, less texts

    Users get used to the reduced visibility and printing of texts can be mitigated by adjusting the reports – still items on the sales order provide a more intuitive handling.

    Further reading

  • Electronic reporting by example: Create a new Bank file definition

    Electronic reporting by example: Create a new Bank file definition

    Electronic Reporting seems to be an integral part of the future of Dynamics 365 that can be used for importing and exporting data of all kinds above and beyond the capabilities of the static data entities, so it is worth finding out how this works.
    In the following we will do this by example.

    Online ressources

    As a starting point there is standard microsoft documentation and this great blog with detaillied instructions:

    High level overview for creation of new Bank file

    1. Extend a preexisting bank format
    2. Define new export format – Constants
    3. Define new export format – Data fields
    4. Add transformations for correct formatting
    5. Add new calculated fields
    6. Calculate total fields
    7. Add user input parameters
    8. Add validations for readable errors
    9. Activate

    BACS: 1. Extend a preexisting bank format

    It is always easier to start based on preexisting work instead of starting from scratch, so to create a new bank format, try to find a similar one to extend.

    There is already a BACS format provided by Microsoft, however this does not conform to the current specification 18, but this will be out starting point.

    BACS: 2. Define new export format – Constants

    Define new file:

    • Define End of Line character
    • Define Constants

    BACS: 3. Define new export format – Data fields

    Define new file:

    • Define data fields –> map to data source

    BACS: 4. Add transformations

    If the format to be printed does not match the data source transformations are required. Examples:

    • Amounts as cents i.e. decimal number format without decimal separator: 123.45 transforms to 12345
    • Date as Year combined with day in year: YYddd

    BACS: 5. Add new calculated fields

    Define new calculated fields:

    • isCredit

    …click Edit formula

    Note: if fields require new data sources, then the data mapping must be extended.

    BACS: 6. Calculate total fields

    Define new totals:

    • Count of credit line / count of debit lines
    • Sum of credit amount / sum of debit amount

    …click Edit group by

    BACS: 7. User input parameters

    Add User input parameters to allow the user to choose options in the file generation dialog.

    The parameters can be used in formulas:

    BACS: 8. Add validations

    Validation help to give proper error messages that tell the user what is wrong.

    Note: the condition must be true so the error does not occur.

    Other option: make fields mandatory, but the error message is not understandable.

    BACS: 9. Final Step – Activate (Complete)

    Don’t forget to “Complete” your Draft – only then the changes are available.

    Files

  • Deep links available in every form

    Deep links available in every form

    Ievgen Miroshnikov gave me the idea and Microsoft also has some information here.

    The following extends the standard button available in every Dynamics 365 for Finance and Operations form to provide the deep link to the currently select record of the primary form data source.

    Copy the link and share it. The recipient will jump directly to the currently selected record.

    (I wonder why Microsoft doesn’t provide this in standard D365FO – this is actually what you would expect from the button.)

    Snap079660

    using Microsoft.Dynamics.AX.Framework.Utilities;
    using Microsoft.Dynamics.@Client.ServerForm.Contexts;
    
    /// &lt;summary&gt;
    /// The class &lt;c&gt;URLUtility_Extension&lt;/c&gt; contains extension methods for the &lt;c&gt;URLUtility&lt;/c&gt; class.
    /// &lt;/summary&gt;
    [ExtensionOf(classStr(URLUtility))]
    public static class URLUtility_Extension
    {
    public static str generateRecordUrl(str _menuItemName, MenuItemType _menuItemType, DataSourceName _dataSourceName, Map _indexFieldValuesMap, DataAreaId _dataAreaId = curExt())
    {
    System.Uri host                     = SessionContext::Get_Current().Get_RequestUrl();
    UrlHelper.UrlGenerator generator    = new UrlHelper.UrlGenerator();
    generator.MenuItemName              = _menuItemName;
    generator.MenuItemType              = _menuItemType;
    generator.HostUrl                   = host.GetLeftPart(System.UriPartial::Path);
    generator.Company                   = _dataAreaId;
    generator.EncryptRequestQuery       = true;
    
    if (_dataSourceName &amp;&amp; _indexFieldValuesMap)
    {
    MapEnumerator mapEnumerator = _indexFieldValuesMap.getEnumerator();
    
    var requestQueryParameterCollection = generator.RequestQueryParameterCollection;
    
    while (mapEnumerator.moveNext())
    {
    requestQueryParameterCollection.UpdateOrAddEntry(_dataSourceName, mapEnumerator.currentKey(), mapEnumerator.currentValue());
    }
    }
    
    return generator.GenerateFullUrl().AbsoluteUri;
    }
    
    public static str generateRecordUrlFromDataSource(FormDataSource _formDataSource)
    {
    FormRun         formRun         = _formDataSource.formRun();
    str             menuItemName    = formRun.args().menuItemName();
    MenuItemType    menuItemType    = formRun.args().menuItemType();
    DataSourceName  dataSourceName  = _formDataSource.name();
    
    TableId   tableId   = _formDataSource.table();
    DictTable dictTable = new DictTable(tableId);
    DictIndex dictIndex = new DictIndex(tableId, dictTable.primaryIndex());
    
    int     fieldCount          = dictIndex.numberOfFields();
    Map     indexFieldValuesMap = new Map(Types::String, Types::String);
    Common  record              = _formDataSource.cursor();
    FieldId primaryKeyFieldId;
    
    for (int fieldIndex = 1; fieldIndex &lt;= fieldCount; fieldIndex++)
    {
    primaryKeyFieldId = dictIndex.field(fieldIndex);
    
    indexFieldValuesMap.insert(fieldId2Name(tableId, primaryKeyFieldId), any2Str(record.(primaryKeyFieldId)));
    }
    
    return URLUtility::generateRecordUrl(menuItemName, menuItemType, dataSourceName, indexFieldValuesMap, record.DataAreaId);
    }
    
    public static str generateUrl(FormRun formRun)
    {
    str url = next generateUrl(formRun);
    
    if (formRun &amp;&amp; formRun.dataSource(1) &amp;&amp; formRun.dataSource(1).cursor())
    {
    url = URLUtility::generateRecordUrlFromDataSource(formRun.dataSource(1));
    }
    return url;
    }
    }
    
  • Change based alerts using Microsoft Flow

    Change based alerts using Microsoft Flow

    Rob showed how to make Date Based Alerts using Microsoft Flow. So this is a great start into the future of the still missing AX alerts. But of course we want change based alerts for example to be informed about a sales order that has been processed. It does not take to long to find out that this is still not possible in the way it has been in the past. However you can get a message once the sales order reaches a certain status:

    You see that the sales order is hard coded and once the sales order status has changed, you will receive an email every day. So why not use your Android Flow App to disable or delete the Flow?

  • Mass changes to Number Sequences in Dynamics 365 for Operations

    Mass changes to Number Sequences in Dynamics 365 for Operations

    Get Microsoft Dynamics Office Add-in for Excel 2016

    Microsoft Dynamics Office Add-in

    Open Number sequences in Excel

    In D365fO go to Organization administration > Number sequences > Number sequences

    Press Office Button > Open in Excel

    Add format field to Excel

    In Excel add the Format field. (Note: add the Name Field as well! Otherwise it will be reset.)

    Edit data

    Change the format as desired (e.g. remove the dash using excel search & replace)

    Publish

    Press Publish