Decimal number field type with an optional unit

1) What is your original issue/pain point you want to solve?

Zammad’s Object Manager has no data type for decimal numbers. Its list of data
types offers exactly one numeric type — integer. Any value with a fractional
part has no place to go: a price, a length, a weight, a coordinate, a quantity
such as 1.5 hours.

There are two workarounds, and both give something away:

  • an input (text) field keeps the decimals and stays readable, but it stops
    being a number — Zammad can no longer compare it, sort by it or threshold it;
  • an integer field stays comparable, but the decimals are gone. The usual
    answer is to store scaled whole numbers — cents instead of euros, millimetres
    instead of metres — and from that moment on every place that shows the value
    is wrong: the ticket sidebar, overviews, trigger and notification texts, the
    API, CSV exports. An agent reads 123456 where the value is 123.456 m.

Rounding is a part of this that only looks optional. An integer field does not
round at all — it truncates: 10.6 is stored as 10, and -10.6 as -10. As
soon as a value has a fixed number of decimal places, something has to decide
what happens to the digits beyond it, and today that decision is made by the
storage layer instead of by the person who configured the field.

On top of that there is no place to record the unit that gives the number its
meaning. Whether a 1200 is euros, dollars, metres or kilograms currently lives
in the field label, in a second text field, or in the number itself (“1200 EUR”)
— and in the last case the value stops being a number again.

2) Which are one or two concrete situations where this problem hurts the most?

  • A measurement on a ticket. A field holds a length such as 123.456 m. As a
    text field, it cannot be used in an overview like “longer than 100 m” —
    no numeric operator exists for text. Stored as an integer in millimetres it
    becomes comparable, but every agent then reads 123456 in the sidebar and in
    every notification mail, and has to move the decimal point in their head.
  • An approval threshold on an amount. A ticket carries a budget figure, and
    everything above a certain amount has to go to a second approver. With a text
    field the threshold cannot be expressed in an overview, a trigger or a
    scheduler at all, so the check happens by hand — which is exactly the kind of
    check that gets forgotten. With an integer field in cents it works, but the
    amount is displayed wrong everywhere it appears.

3) Why is it not solvable with the Zammad standard?

Because the two available shapes exclude each other, and the operators offered
in overviews, triggers, schedulers and Core Workflows make that visible:

  • an input field offers contains, contains not, is any of, is none of,
    starts with one of, ends with one of — no numeric comparison exists at
    all;
  • an integer field offers is, is not, is less than,
    is less than or equal to, is greater than, is greater than or equal to
    but it cannot hold a fractional value.

So a decimal number is either readable or comparable in Zammad, never both.
Storing a scaled integer buys the comparison back only by making every displayed
value wrong, for everyone, in every channel.

There is also no standard place to store the unit next to the value, which is
why the unit ends up inside the number and takes the last bit of numeric
behaviour with it.

The gap is not version-specific: the list of data types is identical in 7.1 and
in the current 7.2 development branch.

4) What is your expectation/what do you want to achieve?

I would like one general field type for decimal numbers, flexible enough to
cover the different jobs these values do, rather than a separate field type per
purpose. Concretely, I would expect to be able to:

  • store a number with decimal places, without a scaled-integer detour, and have
    it displayed the same way everywhere Zammad shows it;
  • choose how many decimal places the field keeps — two for an amount of money,
    three for a millimetre-accurate length, six for a geographic coordinate;
  • decide how the field rounds to those decimal places, rather than inheriting
    whatever the storage happens to do. This is not a refinement: with a fixed
    number of decimal places, rounding happens either way — the only question is
    whether anyone chose it;
  • optionally give the field a unit that is shown together with the value, as
    free text, so the same field type can serve as a currency field (EUR, USD,
    CHF), as a measurement field (m, km, cm, inch, kg) or as a plain number with
    no unit at all;
  • where the unit varies from record to record, let the user pick it on the
    record — amounts in different currencies, lengths in metres or inches;
  • use the value in conditions in overviews, triggers, schedulers and Core
    Workflows with the numeric operators that integer fields already offer, and
    have those conditions compare it as a decimal.

The part that matters most is the plain one: without a unit, this is simply
the decimal number field that Zammad does not have today.
The unit is what
turns the same field into a currency field or a measurement field, without
needing three separate implementations.

A few examples of what one such field type would cover, to show the range:

Value Decimals Unit Use
12345.67 2 EUR, USD, CHF (per record) budget, order value, quotation
123.456 3 m length, distance, dimension
48.137154 6 latitude or longitude as a single value
1.5 1 h estimated effort
2.75 2 kg weight for a shipment

On rounding, in case it helps to see why I raise it at all. Today the behaviour
is neither configurable nor consistent, and nobody picked it:

  • an integer attribute truncates towards zero — 2.5 becomes 2 and 3.5
    becomes 3;
  • writing into a column with a fixed number of decimal places rounds half away
    from zero — 2.5 becomes 3 and 3.5 becomes 4, which is commercial
    rounding.

For an amount of money, that difference is not cosmetic: accounting in several
places expects banker’s rounding, which would give 2 and 4 for the same two
values. Whichever default you pick, I would like it to be a stated one that an
administrator can change per field, rather than a side effect of how the value
happens to be stored.

To be explicit about the scope: I am not asking for unit conversion. The
field should not turn metres into kilometres or euros into dollars — the unit is
there to give the number its meaning and to display it correctly. Conversion is
a separate discussion and should not be a reason to hold this back.

Your Zammad environment:

  • Average concurrent agent count: varies — I work as a Zammad consultant, so
    this request does not describe one instance but a number of customer
    installations of different sizes.
  • Average tickets a day: varies, for the same reason.
  • What roles/people are involved: admins and agents at customer installations.
    Five customers have asked me for this so far — two with a high need, two with
    a medium need, one with a low need.

Anything else which you think is useful to understand your use case:

The reason I am describing this as one field type rather than as “a currency
field” is that every customer who asked for it wanted a slightly different
version of the same thing. Once the field stores a decimal number and can show a
unit next to it, all of those needs are the same feature.

byPARSE