For 3 years I'm using Clockify to track my work hours on my different projects. Sometimes, I need to create an invoice to bill my work and I use a super simple invoicing system called Xolo.

The Problem

There is no proper programatic gateway between Clockify and Xolo, at least nothing that I could find in minutes.

The solution

Let's have a look at the invoice form of Xolo and try to automate that part!

The form is a collection of rows which have few attributes:

Behind the scenes, it is looking like this:

Now we want to set the value of this fields programmatically based on the CSV that Clockify generates.

We define a dummy dataset where all the required attributes are defined - I convert the CSV from Clockify to this format via Regex.

const data = [
  {
    "description": "Create Issue (07/05/2022)",
    "qty": 0.50,
    "unit": "h",
    "price": 160,
    "discount": 0,
  }
]

We can now write a super basic script to set all the values to the fields that we just run in the Chrome console.


const sleep = m => new Promise(r => setTimeout(r, m))


let allTasks = data.map( (row, index) => {
    return async () => {
        const desc_sel = $('#invoice-description-' + index)[0]
        desc_sel.value = row.description;
        desc_sel.dispatchEvent(new CustomEvent('onchange'))

        const desc_qty = $('#invoice-quantity-' + index)[0]
        desc_qty.value = row.qty;
        desc_qty.dispatchEvent(new CustomEvent('onchange'))

        const desc_unit = $(`select[name="items[${index}].unit"]`)[0]
        desc_unit.value = row.unit;
        desc_unit.dispatchEvent(new CustomEvent('change'))

        const desc_price = $(`#invoice-price-${index}`)[0]
        desc_price.value = row.price;
        desc_price.dispatchEvent(new CustomEvent('onchange'))

        const desc_discount = $(`#invoice-discount-${index}`)[0]
        desc_discount.value = row.discount;
        desc_discount.dispatchEvent(new CustomEvent('onchange'))

        await sleep(500);
        desc_discount.dispatchEvent(new CustomEvent('input', {'bubbles': true}))
        $('#add-new-row').click();
    }
});

const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

serial(allTasks);

And then, we have it, we have all our tasks in our invoice! We just need a final check and the invoice can be sent to the customer!

This way, I save 1 hour per month and you can do it too ! 🌞

A bit of tech

Although the script is quite simple, it is interesting to see that we need to trigger few input events manually in order to make the form work as there are some js listener on these fields.

Also, the last dispatched event desc_discount.dispatchEvent(new CustomEvent('input', {'bubbles': true})) must bubble so that it notifies the parent (the form) and the parent then triggers the Xolo calculation (which sets the brutto and netto amounts + validates the invoice).