Convert Numbers to Words in Deluge for Zoho Books Invoices

Convert a Number to Words on a Zoho Books Invoice

Invoices in much of Latin America and Europe must state the total in words as well as figures — “TWO THOUSAND FOUR HUNDRED FIFTY 00/100 MXN”. Zoho Books has no built-in field for it. This is a pure Deluge implementation, so it works offline, costs nothing, and cannot break because someone else’s website went down.

The external dependency is gone. The original called http://beautifytools.com/num-to-word.php — a third-party website, over plain HTTP, in the middle of your invoicing. That is a hard dependency on a stranger for a calculation your own code can do in thirty lines, and it puts invoice totals across the public internet unencrypted. If that site changes its response format or disappears, your invoices silently lose their amount-in-words. It has been replaced with a self-contained function. The original also called zoho.books.updateRecord without the now-mandatory connection argument.

Part 1 — the three-digit helper

Create this as a standalone function. It handles 1–999, which is the only hard part; everything above is repetition.

string Utils.hundredsToWords(int n)
{
    ones = {"","one","two","three","four","five","six","seven","eight","nine","ten",
            "eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen",
            "eighteen","nineteen"};
    tens = {"","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"};

    remainder = n;
    out = "";

    if(remainder >= 100)
    {
        // Subtract the remainder first so the division is exact
        hundreds = ((remainder - (remainder % 100)) / 100).toLong();
        out = ones.get(hundreds) + " hundred";
        remainder = remainder % 100;
        if(remainder > 0)
        {
            out = out + " ";
        }
    }

    if(remainder >= 20)
    {
        tensDigit = ((remainder - (remainder % 10)) / 10).toLong();
        out = out + tens.get(tensDigit);
        if(remainder % 10 > 0)
        {
            out = out + "-" + ones.get(remainder % 10);
        }
    }
    else if(remainder > 0)
    {
        out = out + ones.get(remainder);
    }
    return out;
}

Part 2 — the full number

This walks the number in groups of three and tags each with its scale.

string Utils.numberToWords(int n)
{
    if(n == 0)
    {
        return "zero";
    }

    scales = {""," thousand"," million"," billion"};
    parts  = List();
    remainder = n;
    idx = 0;

    while(remainder > 0)
    {
        chunk = remainder % 1000;
        if(chunk > 0)
        {
            parts.add(thisapp.Utils.hundredsToWords(chunk) + scales.get(idx));
        }
        remainder = ((remainder - chunk) / 1000).toLong();
        idx = idx + 1;
    }

    // parts were built smallest-first, so walk it backwards
    out = "";
    i = parts.size() - 1;
    while(i >= 0)
    {
        out = out + parts.get(i);
        if(i > 0)
        {
            out = out + " ";
        }
        i = i - 1;
    }
    return out;
}

Part 3 — write it onto the invoice

// Books custom function, triggered on invoice creation
invoiceId    = invoice.get("invoice_id");
invoiceTotal = invoice.get("total");
currencyCode = invoice.get("currency_code");

wholePart = invoiceTotal.toLong();
cents     = ((invoiceTotal - wholePart) * 100).round(0).toLong();

centsText = cents.toString();
if(centsText.length() == 1)
{
    centsText = "0" + centsText;
}

amountInWords = thisapp.Utils.numberToWords(wholePart).toUpperCase()
                + " " + centsText + "/100 " + currencyCode;

customField = Map();
customField.put("customfield_id","YOUR_CUSTOM_FIELD_ID");
customField.put("value",amountInWords);

customFields = List();
customFields.add(customField);

updateMap = Map();
updateMap.put("custom_fields",customFields);

resp = zoho.books.updateRecord("invoices","YOUR_BOOKS_ORG_ID",invoiceId,updateMap,"zoho_books_connection");
info resp;

Notes

  • Why the subtraction before dividing. Deluge’s / does not always return an integer, and a decimal index throws when you pass it to list.get(). Removing the remainder first makes the division exact, and .toLong() guarantees the type. This is the bug people hit when they port this from another language.
  • Cents come from arithmetic, not string slicing. The original took the text after the decimal point, which gives "5" for 10.50 and "" for a whole number. Multiplying the fractional part by 100 and rounding is correct in both cases.
  • Spanish output. Swap the two word lists for {"","uno","dos","tres"...} and {"","","veinte","treinta"...}. The structure holds; Spanish needs a special case for ciento versus cien and the 21–29 contractions.
  • Use customfield_id, not index. The id is stable; the index shifts the moment you reorder your custom fields.

This script is part of the free Creator Scripts Deluge Library.

All 39 Deluge scripts, the full Zoho Creator course, and every downloadable asset are now free. Get free access →