Summary
Reported by Patrik Lundgren via the community Slack:
I just saw that convert is implemented for aggregations, great, thanks, that's exactly what I'm interested in. It would also be good if it was possible to add convert to other currency fields as well.
Today :convert is an aggregation-time instruction only. {SUM:Lines.Amount:currency:EUR:convert} converts each row before summing. A plain field tag — {Amount:currency:EUR:convert} — does not convert.
The part that makes this more than a feature gap
:convert on a plain field isn't rejected. It's silently misread as a locale.
formatCurrency (DocGenService.cls:5974) reads the third segment as the locale:
String iso = spec.toUpperCase();
String locale = segments.size() >= 3 ? segments[2].trim() : null;
return formatCurrencyWithIso(num, iso, locale);
So {Amount:currency:EUR:convert} arrives as iso='EUR', locale='convert'. Every locale helper tolerates unknown input and falls through to its default — getLocaleGroupSep returns ',', getLocaleDecSep returns '.', isCurrencyAfter returns false. Nothing throws.
Result: the original, unconverted amount is rendered with the target currency's symbol. A 10,000 SEK line prints as €10,000.00. No error, no warning, and the number looks entirely plausible.
That's a severity:silent-corruption shape reachable by an author making a reasonable assumption — that a modifier documented for aggregates also works on the field it aggregates. Patrik's request implies they expect exactly that.
Worth confirming on a real multi-currency org before sizing the fix — the reasoning above is from reading the segment parsing, not from a generated document.
Two things to fix
- Stop the silent misread. Whatever happens with the feature,
:convert in the locale slot should not quietly produce an unconverted number under a foreign symbol. Either honour it or raise the same actionable error the aggregate path already raises when rates are missing (DocGenCurrency.cls:120).
- Implement conversion for plain fields. The machinery exists —
DocGenCurrency.convert() and the CurrencyType rate cache are already there and already used by the aggregate path. The missing piece is the per-row source currency: an aggregate knows each row's CurrencyIsoCode because it walks the rows, whereas a plain field tag needs the ISO of the record the field belongs to. resolveCurrencyIso(data, sourceField) — already used by {…:currency:auto} — looks like the right existing hook.
Known constraint to carry over
Conversion uses static CurrencyType rates. Advanced Currency Management's dated rates (DatedConversionRate) are deliberately not consulted, because applying today's rate to a historical row would be silently wrong — see the note on DocGenCurrency.getRates(). Same limitation applies here and should be documented rather than quietly inherited.
Scope
DocGenService.formatCurrency / formatCurrencyWithIso (~5955-6000), plus DocGenCurrency for the conversion call and the missing-rate error path. Aggregates already strip :convert before formatting (DocGenService.cls:6289), so that path shouldn't need changing — but confirm the strip doesn't now remove a segment the plain-field path needs.
Add an e2e-07-syntax5 assertion covering a plain-field convert, and one proving the silent-misread case no longer renders an unconverted number.
Related
First half of the same Slack report is filed separately — rendering the ISO code instead of an ambiguous symbol (kr for SEK/NOK/DKK). The two compose: an author converting to a Nordic currency currently gets both problems at once.
Summary
Reported by Patrik Lundgren via the community Slack:
Today
:convertis an aggregation-time instruction only.{SUM:Lines.Amount:currency:EUR:convert}converts each row before summing. A plain field tag —{Amount:currency:EUR:convert}— does not convert.The part that makes this more than a feature gap
:converton a plain field isn't rejected. It's silently misread as a locale.formatCurrency(DocGenService.cls:5974) reads the third segment as the locale:So
{Amount:currency:EUR:convert}arrives asiso='EUR',locale='convert'. Every locale helper tolerates unknown input and falls through to its default —getLocaleGroupSepreturns',',getLocaleDecSepreturns'.',isCurrencyAfterreturnsfalse. Nothing throws.Result: the original, unconverted amount is rendered with the target currency's symbol. A 10,000 SEK line prints as
€10,000.00. No error, no warning, and the number looks entirely plausible.That's a
severity:silent-corruptionshape reachable by an author making a reasonable assumption — that a modifier documented for aggregates also works on the field it aggregates. Patrik's request implies they expect exactly that.Worth confirming on a real multi-currency org before sizing the fix — the reasoning above is from reading the segment parsing, not from a generated document.
Two things to fix
:convertin the locale slot should not quietly produce an unconverted number under a foreign symbol. Either honour it or raise the same actionable error the aggregate path already raises when rates are missing (DocGenCurrency.cls:120).DocGenCurrency.convert()and theCurrencyTyperate cache are already there and already used by the aggregate path. The missing piece is the per-row source currency: an aggregate knows each row'sCurrencyIsoCodebecause it walks the rows, whereas a plain field tag needs the ISO of the record the field belongs to.resolveCurrencyIso(data, sourceField)— already used by{…:currency:auto}— looks like the right existing hook.Known constraint to carry over
Conversion uses static
CurrencyTyperates. Advanced Currency Management's dated rates (DatedConversionRate) are deliberately not consulted, because applying today's rate to a historical row would be silently wrong — see the note onDocGenCurrency.getRates(). Same limitation applies here and should be documented rather than quietly inherited.Scope
DocGenService.formatCurrency/formatCurrencyWithIso(~5955-6000), plusDocGenCurrencyfor the conversion call and the missing-rate error path. Aggregates already strip:convertbefore formatting (DocGenService.cls:6289), so that path shouldn't need changing — but confirm the strip doesn't now remove a segment the plain-field path needs.Add an
e2e-07-syntax5assertion covering a plain-field convert, and one proving the silent-misread case no longer renders an unconverted number.Related
First half of the same Slack report is filed separately — rendering the ISO code instead of an ambiguous symbol (
krfor SEK/NOK/DKK). The two compose: an author converting to a Nordic currency currently gets both problems at once.