Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 3 Next »

Components

Repositories

Backend

https://github.com/openimis/openimis-be-report_py/

Frontend

There is no frontend module for reports

Dependencies

Backend

The openIMIS report module provides the core functionality of the reports but does not actually implement the reports. According to the modularity principles, each module is able to provide his own reports.

The reports are based on the ReportBro engine and are able to produce PDF files as well as XLSX (Microsoft Excel). The report templates can be visually edited in openIMIS to customize them.

The report backend module exposes the necessary API to list available reports, execute and edit them. Each report can have its own filters. This means that the openIMIS report module is generally not able to run all reports without some frontend customization. Just like in the backend, of course, each module can provide the specific requirements for its own reports.

Since the reports are running native code on the server with full access to the data, it is not possible to create a report entirely from the user interface.

Creating a report

Backend module

Report definitions

The starting point of the report module is the <app>/report.py file that contains the list of available reports with their configuration:

report_definitions = [
    {
        "name": "insuree_family_overview",
        "engine": 0,
        "default_report": insuree_family_overview.template,
        "description": "Simple claim report",
        "module": "insuree",
        "python_query": insuree_family_overview_query,
        "permission": ["131215"],
    },
    {
        "name": "enrolled_families",
        "engine": 0,
        "default_report": enrolled_families.template,
        "description": "Enrolled families",
        "module": "insuree",
        "python_query": enrolled_families_query,
        "permission": ["131215"],
    },
]

name

Name of the report that will be exposed

engine

0 for ReportBro, this is currently the only engine available

default_report

This specifies the report template to use when there is no local customization yet. This can be a simple string but we recommend to load that definition from a dedicated file

description

Human readable description of the report

module

The reports are exposed within a module, this should be the same as the current module name.

python_query

A report needs data. This specifies the Python function to be called to fetch the data and present it in the format required by the report template.

permission

This is an array of permissions that allow to run the report. They are exactly the same as in the various other features of the application.

Data fetching function

The above examples use two versions of the same report, one using the stored procedure, the other using native Python code.

The native function looks like this:

def insuree_family_overview_query(user, date_from=None, date_to=None, **kwargs):
    ...
    if date_from:
        filters &= Q(validity_from__gte=date_from)
    ...
    queryset = (
        Insuree.objects.filter(filters)
        .values(
            "chf_id",
            "other_names",
            "last_name",
            enroll_date=F("validity_from"),
            ...
    return {"data": list(queryset)}

The function will always get the logged user as first parameter. All subsequent parameters are named and optional. Their names correspond to the expected report parameters. Adding **kwargs allows the function to receive additional parameters without crashing and should be kept.

⚠️ The parameters are passed in the URL and have to be thoroughly checked before use. Passing them directly to the database could be a security issue.

This abstract shows that we’re filtering according to the parameters and then the query is run. The function returns a dictionary that will be passed to the template.

Keep in mind that ReportBro has limited support for data types. Passing complex objects in this dictionary might not be supported in the template. Refer to the ReportBro documentation and test the data in the Designer.

Alternatively, existing stored procedures can also be wrapped into reports like our second example:

from report.services import run_stored_proc_report

def enrolled_families_query(user, date_from=None, date_to=None, location_id=None, **kwargs):
    data = run_stored_proc_report(
        "uspSSRSEnroledFamilies",
        LocationId=location_id,
        StartDate=date_from,
        EndDate=date_to,
    )
    return {
        "data": data
    }

The parameters are the same but the run_stored_proc_report will call the specified stored procedure, converting the location_id parameter to LocationIdfor the stored procedure. The data returned by the procedure is passed as an array of dict. Using this method, the report template variables have to match the stored procedure output column names.

Backend API

The first GraphQL API allows to browse the available reports:

query useReportsQuery {
  reports {
    name
    module
    description
  }
}

Running the actual report is a direct HTTP call:

https://<server>/api/report/insuree/enrolled_families/pdf/?date_from=2022-01-01

/api/report/

branches to the report module

insuree

name of the module containing the report that we want to run

enrolled_families

name of the report

pdf

format of the report, could be xlsx

?date_from=2022-01-01

report parameter

ReportBro template

The openIMIS Web interface includes the ReportBro Designer. To create a new report template, the easiest is to use that Designer and then to use the button to output the template to the browser console:

Here are some screenshots of the ReportBro Designer:

Frontend

As described above, the logic for the parameters of reports can vary greatly and are therefore left for the frontend to handle.

First of all, the index.js should contain the report definition:

const DEFAULT_CONFIG = {
  "translations": [{ key: "en", messages: messages_en }],
  "reducers": [{ key: "insuree", reducer }],
  "reports": [
    {
      key: "insuree_family_overview",
      component: InsureeFamilyOverviewReport,
      isValid: (values) => values.dateFrom && values.dateTo,
      getParams: (values) => ({
        dateFrom: values.dateFrom,
        dateTo: values.dateTo,
      }),
    },

The definition is pretty self-explanatory with the report key corresponding to the backend report key, isValid for required parameters, getParams that formats the parameters to send to the backend.

Here is the example of the report component that puts it together:

const InsureeFamilyOverviewReport = (props) => {
  const { values, setValues } = props;

  return (
    <Grid container direction="column" spacing={1}>
      <Grid item>
        <PublishedComponent
          pubRef="core.DatePicker"
          value={values.dateFrom}
          module="insuree"
          required
          label="InsureeFamilyOverviewReport.dateFrom"
          onChange={(dateFrom) => setValues({ ...values, dateFrom })}
        />
      </Grid>
      <Grid item>
        <PublishedComponent
          pubRef="core.DatePicker"
          value={values.dateTo}
          module="insuree"
          required
          label="InsureeFamilyOverviewReport.dateTo"
          onChange={(dateTo) => setValues({ ...values, dateTo })}
        />
      </Grid>
    </Grid>
  );
};

export default InsureeFamilyOverviewReport;

  • No labels