Electronic Reporting (ER) Cookbook 3: Working with dates

Electronic Reporting (ER) Cookbook 3: Working with dates

Following the parts 1 and 2 of my hints and tips collection for Electronic reporting, let me shed some light on working with dates and date formatting.

Cut-off date

If a classic Excel transaction report must be implemented in the Electronic Reporting module, then a cut-off date is a common requirement: show all transactions up to and including a certain date, or show all transactions if the user hasn’t selected any date. Classically, a parameter in the dialog is presented to the user, this requires a User parameter of the DATE type in the format or – better – in the date model mapping (see also Electronic reporting in depth).

This parameter must be used in a Calculated field of the Record List type. Below is a snippet from one of my very first reports dated back to 2016; it extracts ledger transactions from the current company for the German GDPdU dump file:
WHERE(GLaccountEntry,
AND(
NOT(GLaccountEntry.AccountingCurrencyAmount=0),
GLaccountEntry.'>Relations'.GeneralJournalEntry.Ledger = Ledger.'current()',
GLaccountEntry.'>Relations'.GeneralJournalEntry.'>Relations'.FiscalCalendarPeriod.Type = FiscalPeriodType.Operating,
GLaccountEntry.'>Relations'.GeneralJournalEntry.AccountingDate >= FromDate,
OR(GLaccountEntry.'>Relations'.GeneralJournalEntry.AccountingDate <= ToDate, ToDate=NULLDATE())))

In hindsight, this was a terrible implementation. The WHERE operator works with in-memory lists. In essence, it loads all transactions from all companies and all periods and years into an internal XML container, and THEN starts filtering it by company and date. Not surprisingly, the performance degrades rapidly and the report execution time grows exponentially.

The FILTER is a better function as it compiles to a direct SQL statement and returns a reduced dataset; however, it has limited capabilities and does not support cross joins as above.

A mature solution would be to perform the filtering in 2 steps: one Calculated list fetches a maximally pre-filtered list from the SQL database, and subsequent Calculated field variables apply additional dynamic filters to this subset.
The below expression returns a list of all cost project transactions up to a certain date:
FILTER(ProjTransPosting,
AND(ProjTransPosting.PostingType=Enums.LedgerPostingType.ProjCost,
OR(ProjTransPosting.LedgerTransDate <= $ToDate, $ToDate=NULLDATE())))

where $ToDate is the user parameter.

To simplify and reuse the classic pattern WHERE LedgerTransDate <= $ToDate OR $ToDate=NULLDATE(), an interim variable (Calculated field) $ToDateOrInfinity may be declared (I could not find any other way to pass a date literal into an ER expression, that’s why the crude DATEVALUE(“31-12-2154″,”dd-MM-yyyy”)):
IF('$ToDate'>NULLDATE(), '$ToDate', DATEVALUE("31-12-2154","dd-MM-yyyy"))
and the above query reduces to just
FILTER(ProjTransPosting, AND(ProjTransPosting.PostingType=Enums.LedgerPostingType.ProjCost,
ProjTransPosting.LedgerTransDate <= $ToDateOrInfinity))

ToDate user parameterThis led to an issue on the UI, however. User parameters from the [default] Mapping designer propagate to the format, are merged with the user parameters defined in the Format designer and displayed together in the common dialog window. According to Maxim B. from the Microsoft R&D Center in Moscow, a user parameter is shown at the run time if 2 conditions have been met:

  • the visibility is not turned off;
  • in the mapping, the user parameter is bound to the model either directly or indirectly.

Seemingly, the above indirect usage obscures the parameter from the ER format and it disappears from the dialog. The solution was to bind $ToDate directly to an unused field in the model.

Date formatting: DATETIME to DATE

I spent quite some time trying to find an embedded function to convert a DATETIME type and bind to a DATE cell in Excel. Obviously, you can declare the format node a STRING and apply the DATETIMEFORMAT() function, but this circumvents the locale settings.

The solution was dumb, but it worked:
DATEVALUE(DATETIMEFORMAT(@.'Created date', "dd-MM-yyyy"), "dd-MM-yyyy")

First, Last date of the month

The below formulas work in model mappings. Assume the parmPeriodDate a user parameter. The first formula is easy and trivial:
DATEVALUE("01-"&DATEFORMAT(parmPeriodDate, "MM-yyyy"), "dd-MM-yyyy")

The second formula is crazy. It needs a declaration of the system class DateTimeUtil nearby in the model mapping data source section.
DATEVALUE(DATETIMEFORMAT(ADDDAYS(DATETODATETIME(DATEVALUE("01-"&
DATETIMEFORMAT(DateTimeUtil.addMonths(DATETODATETIME(parmPeriodDate),1),"MM-yyyy"), "dd-MM-yyyy")),-1),"dd-MM-yyyy"),"dd-MM-yyyy")

It takes the parameter (e.g. 15.02.2023), adds a month (15.03.2023), makes the 1st of that month (01.03.2023), and subtracts 1 day (28.02.2023).

Null date transformation

Date formatting: Show an empty date in Excel as blank

An empty date in the format data source is printed in Excel as 02.01.1900. This is not good and very distracting. Again, an obvious solution is DATEFORMAT() in a string type cell, but it just doesn’t feel right.
An elegant approach is a generic Transformation in the Format designer: Formula transformation NoNullDate =
IF(parameter<=NULLDATE()+1, "", DATEFORMAT(parameter, "dd-MM-yyyy"))
This can be re-used and quickly applied to every node:

On currency in credit card expense transactions – Part 2

On currency in credit card expense transactions – Part 2

In the previous blog On currency in credit card expense transactions – Part 1 we have explored a case where the credit card currency and the local accounting currency in D365FO did match. But what if the card is issued in a currency different from the accounting currency of the legal entity?

Case 2: credit card currency <> accounting currency

For example, Hungarian employees (the official currency of Hungary is the Forint, abbreviated as HUF) may be given credit cards billed in Euro, since they are surrounded by the Euro zone. If we follow the same logic as before, if such a credit card statement is expressed in EUR, then it must be posted in EUR into the AP subledger:
  • 01.06.2019 Paid a hotel in Bratislava 500 EUR (the credit card provider applied the cross rate of 1.00)
  • 04.06.2019 Posted in AP subledger as 500 EUR (the daily rate was 322 HUF/EUR)
  • 30.06.2019 The credit card balance of June was withdrawn by the bank from the employee’s daily account in HUF @323,39 = 161 695 HUF. This is not recorded in D365FO for personal credit cards.
  • 30.06.2019 An AP revaluation is performed in D365FO. The liability is now worth 161 450 HUF at the current exchange rate of 322,9 HUF/EUR
  • 01.07.2019 The employee is reimbursed in the local currency by the employer with 161 450 HUF @322,9 HUF/EUR.
As a result, the employee will suffer a loss of 245 HUF but have to arrange himself/herself, as the benefits of travelling with an Euro card should overweigh any potential losses from the imperfect execution in D365FO by far.

Case 3: credit card currency <> accounting currency <> local currency

What if the trip was not to Slovakia (EUR) but to the Czech Republic who refused to adopt the Euro and retained their own Czech koruna (CZK)? Now all 3 currencies in the transaction are different:
  • The legal entity Accounting currency = HUF
  • The transaction Local currency = CZK
  • The Credit card Currency = EUR
The equation is still the same, the credit card balance is posted as a Forex liability in EUR. In the General Ledger voucher / accounting distribution the cross rate CZK -> HUF is kind of triangulated through the Euro: CZK -> EUR -> HUF where CZK -> EUR is the rate applied by the credit card institution. With that in mind, let us summarize the different business cases with the 3rd being the most generic and sound:
Case 1 Case 2 Case 3
Credit card Currency CHF EUR EUR
CC “Local” currency THB EUR CZK
Accounting currency CHF HUF HUF
Expense line THB EUR CZK
Line itemizations THB EUR CZK
Acc. distribution THB EUR CZK*
GL voucher Dr THB – Cr THB** Dr EUR – Cr EUR Dr CZK – Cr CZK**
AP subledger CHF EUR EUR
(*) The accounting currency HUF equivalent i.e. ‘amount MST’ is evaluated through the rate EUR -> HUF (**) Note the currency mismatch between the AP and GL ledgers. This spoils the AP ledger account with transactions in foreign currencies and may potentially kick the subledgers off balance.

On currency in credit card expense transactions – Part 1

On currency in credit card expense transactions – Part 1

Introduction

The Expense management module in D365FO comes with an interface to credit card providers. I addressed this topic in an old blog of mine: visa-vcf-plain-text-credit-card-statement-into-dynamics-ax-with-xslt. Imported credit card transactions end up in the [Unattached] Credit card transactions list, ready to be taken over by the employee into an expense report.

There used to be a lot of misunderstanding with regards to the different currency rates and amounts and their meaning. Moreover, for a decade the Dynamics system had been posting the liability in the merchant transaction currency, which led to the expense report being split by currency code on posting into the AP ledger. It was wrong in many ways until we fixed it in a tremendous collaboration effort with Microsoft Support in 2018.

Case 1: credit card currency = accounting currency

In the expense report line there is an obligatory Payment method associated with the credit card type. It mandates how the employee is reimbursed for this part of the expense report. Typically, the Payment method (Expense management > Setup > General > Payment methods) is configured with the Offset account type = Worker which creates a liability against the AP account (vendor) associated with the employee.

If this liability is expressed in a currency different from the accounting currency of the legal entity, it starts accumulating exchange rate difference. If the payment takes place in a different accounting period, the employee may be reimbursed too much or too little. Indeed, the credit card it typically bound to a bank account of the employee, and the exchange rate charged by the bank to the cardholder is very unlikely to be the same as the current corporate exchange rate maintained in D365FO. Ergo, the AP transaction should be expressed in the accounting currency.

Take a look at the below example, the employee visited Thailand:
Credit card transaction

  • The Local currency code is not what you would expect but the currency the merchant charged the transaction in, here the Thai baht. Internally it is called EXCHCODE_LOCALCURRENCY.
  • Amount in local currency is the transaction amount in baht
  • The Currency code is the currency the credit card issued in (a.k.a. EXCHCODE_CREDITCARDCURRENCY), typically the currency of the state where the cardholder lives (here: the Swiss Franc)
  • Transaction amount is the amount the credit card issuer will settle against the bank account backing the credit card, i.e. an amount in CHF to be withheld from the cardholder at the end of the month. Internally it is called AMOUNT_CREDITCARDCURRENCY.

The exchange rate between the “Local currency” and the “Currency” must not follow the standard exchange rate setting in D365FO, but it should be what the credit card institute provides on the account statement. Take the next example (Expense management > Periodic tasks > Credit card transactions):
Credit card transactions
The exchange rate was developing over the same time span as shown below:
01.01.2018 2,995 CHF for 100 THB
01.02.2018 2,975 CHF for 100 THB
01.03.2018 3,001 CHF for 100 THB

The exchange rate applied by the bank in February was 15,11 CHF/ 500 THB = 0,03022 CHF/THB = 3,022 CHF for 100 THB. Clearly, it was higher than the average to the disadvantage of the employee, right as expected of a greedy bank.

What will you see in Dynamics 365 for Finance and Operations (version 8.x+, all bugs fixed) upon attaching the imported credit card transactions to an expense report and posting the same?
Credit card transaction detailed

  • Expense report line currency: THB
  • Expense report line Transaction amount and currency: 77,67 CHF (!)
  • Expense report line Local amount: 2550
  • Accounting distribution currency and amount: 2550 THB
  • Expense report line Exchange rate: 3,046 CHF/100 THB, not editable (*)
  • GL Voucher transactions: Debit expense THB – Credit liability THB (**)
  • AP Subledger transaction: Credit liability CHF (!!)

(*) This is the unique exchange rate as charged by the credit card provider, and not the default legal entity exchange rate on the transaction date.

(**) The GL transaction currency does not match the subledger currency, as you can see. In theory, it is possible to have voucher transactions with the debit in one currency (THB) and a credit on another currency (CHF), as long as the equivalent in the local currency (CHF) matches and the voucher is in balance. However, it was not feasible for Microsoft to implement it in the subledger / accounting distribution programming model.

To be continued: Part 2