Implementation of Event Invoice view using Ember Tables

This blog post emphasizes the power of ember tables and how it was leveraged to implement the event invoice view for eventyay. Event Invoices can be defined as the fee given by the organizer for hosting the event on the platform. 

Eventyay is the outstanding Open Event management solution using standardized event formats developed at FOSSASIA. Porting from v1 to v2, event invoices are an integral part in the process. 

Initially, throughout the whole project, plain HTML tables were utilized to render data pertaining to sales, tickets info etc. This in turn made the task of rendering data a cumbersome one. To implement clean & ubiquitous tables with in-built search & pagination functionalities, the ember addon ember-table has been used in Eventyay v2.

To integrate ember tables, the HTML tables had to be replaced with the ember-table component which was created in the Open Event Project. 

To utilize this component, column names for upcoming, paid and due invoices are required. These are stored in Plain Old Javascript Objects (POJOs) in the controller logic passed to the appropriate ember-table component.

In the template logic, we check for the params i.e invoice status in this case and render the ember table through a component. Certain parameters such as the searchQuery, metaData, filterOptions etc. were to be passed in for total control of the table.

@computed()
  get columns() {
    let columns = [];
    if (this.model.params.invoice_status === 'upcoming') {
      columns = [
        {
          name      : 'Invoice ID',
          valuePath : 'identifier'
        },
        {
          name          : 'Event Name',
          valuePath     : 'event',
          cellComponent : 'ui-table/cell/events/cell-event-invoice'
        },
        {
          name      : 'Date Issued',
          valuePath : 'createdAt'
        },
        {
          name            : 'Outstanding Amount',
          valuePath       : 'amount',
          extraValuePaths : ['event'],
          cellComponent   : 'ui-table/cell/events/cell-amount'
        },
        {
          name      : 'View Invoice',
          valuePath : 'invoicePdfUrl'
        }
      ];
    } else if (this.model.params.invoice_status === 'paid') {
      columns = [
        {
          name      : 'Invoice ID',
          valuePath : 'identifier'
        },
        {
          name          : 'Event Name',
          valuePath     : 'event',
          cellComponent : 'ui-table/cell/events/cell-event-invoice'
        },
        {
          name      : 'Date Issued',
          valuePath : 'createdAt'
        },
        {
          name            : 'Amount',
          valuePath       : 'amount',
          extraValuePaths : ['event'],
          cellComponent   : 'ui-table/cell/events/cell-amount'
        },
        {
          name      : 'Date Paid',
          valuePath : 'completedAt'
        },
        {
          name      : 'View Invoice',
          valuePath : 'invoicePdfUrl'
        },
        {
          name            : 'Action',
          valuePath       : 'identifier',
          extraValuePaths : ['status'],
          cellComponent   : 'ui-table/cell/events/cell-action'
        }

      ];
    } else if (this.model.params.invoice_status === 'due') {
      columns =   [
        {
          name      : 'Invoice ID',
          valuePath : 'identifier'
        },
        {
          name          : 'Event Name',
          valuePath     : 'event',
          cellComponent : 'ui-table/cell/events/cell-event-invoice'

        },
        {
          name      : 'Date Issued',
          valuePath : 'createdAt'
        },
        {
          name            : 'Amount Due',
          valuePath       : 'amount',
          extraValuePaths : ['event'],
          cellComponent   : 'ui-table/cell/events/cell-amount'
        },
        {
          name      : 'View Invoice',
          valuePath : 'invoicePdfUrl'
        },
        {
          name            : 'Action',
          valuePath       : 'identifier',
          extraValuePaths : ['status'],
          cellComponent   : 'ui-table/cell/events/cell-action'
        }

      ];
    } else if (this.model.params.invoice_status === 'all') {
      columns = [
        {
          name      : 'Invoice ID',
          valuePath : 'identifier'
        },
        {
          name          : 'Event Name',
          valuePath     : 'event',
          cellComponent : 'ui-table/cell/events/cell-event-invoice'
        },
        {
          name            : 'Amount',
          valuePath       : 'amount',
          extraValuePaths : ['event'],
          cellComponent   : 'ui-table/cell/events/cell-amount'
        },
        {
          name      : 'Status',
          valuePath : 'status'
        },
        {
          name            : 'Action',
          valuePath       : 'identifier',
          extraValuePaths : ['status'],
          cellComponent   : 'ui-table/cell/events/cell-action'
        }

      ];
    }
    return columns;
  }
}

In the route logic, the queryObject is defined to specify the default page size, page numbers and the filter applied to query the event invoice model. A sorting filter is additionally applied to pre-sort the entries before rendering it. We return the data and the params under the model hook which is then used inside the template.

For the implementation of filters, we specify the type of filters we use by storing them in a filterOptions object. For paid and due invoices, we query the event invoice model, checking if the invoice_status is paid or due. The challenge for upcoming invoices was that we won’t be able to name every other invoice as an upcoming one. Therefore, the solution which was utilized here was to query only those events which whose createdAt(date created) attribute was less than 30 days and those which weren’t deleted using Soft Deletion.

These invoices were rendered using the tabbed navigation component complying with the existing code practices.

  async model(params) {
    this.set('params', params);
    const searchField = 'name';
    let filterOptions = [];
    if (params.invoice_status === 'paid' || params.invoice_status === 'due') {
      filterOptions = [
        {
          name : 'status',
          op   : 'eq',
          val  : params.invoice_status
        }
      ];
    } else if (params.invoice_status === 'upcoming') {
      filterOptions = [
        {
          and: [
            {
              name : 'deleted-at',
              op   : 'eq',
              val  : null
            },
            {
              name : 'created-at',
              op   : 'ge',
              val  : moment().subtract(30, 'days').toISOString()
            }
          ]
        }
      ];
    }


    filterOptions = this.applySearchFilters(filterOptions, params, searchField);

    let queryString = {
      include        : 'event',
      filter         : filterOptions,
      'page[size]'   : params.per_page || 10,
      'page[number]' : params.page || 1
    };

    queryString = this.applySortFilters(queryString, params);
    return {
      eventInvoices: (await this.store.query('event-invoice', queryString)).toArray(),
      params

    };

  }

Model Hook for ember invoice tables

Resources:

Related work and code repo:

Tags:

Eventyay, FOSSASIA, Flask, Ember Tables, SQLAlchemy, Open Event, Python

Close Menu