Skip to content
Stefan Dombek edited this page Mar 11, 2024 · 29 revisions

Finances

Table of Contents

1. General

1.1. What does the Finance app do?

The Finance app allows you to create fund structures and manage money.

[top]

1.2. Transactions

The transactions from the Finance app provide reliable data on the financial flows within FOLIO. You can also use the data from the different apps, but these may be inaccurate. Within the app, transactions are created that are assigned to a fiscal year, budget, ledger, fund and, if applicable, an expense class. In the various FOLIO apps, where financial data play a role, a UUID can be used to link to a specific transaction in the Finance app.

[top]

1.3. Foreign Currencies

Foreign currencies can be entered in the apps in FOLIO. These are converted into the set system currency in the Finance app transactions. The exchange rate can be generated automatically or entered manually.

[top]

1.4. Expense Classes

A fiscal entity used to track transactions against a specific purpose or function within a Fund. Optional, tenant-defined, and can be assigned to one or more funds. Applied to order lines and invoice lines during fund distribution. Each fund can support multiple expense classes.

[top]

2. Querying financial data from the FOLIO App Finance

All queries shown here are for Metadb.

2.1. Querying funds by fiscal year

In FOLIO there is the possibility to use a fund over several fiscal years. This is the case, for example, when a Fiscal Year Rollover (FYRO) is to carry over the encumbrances into the new year. Because a fund is linked to a UUID and it remains the same after FYRO, the tables must be linked in a specific way.

Example:

SELECT
    *
FROM
    folio_finance.fiscal_year
    LEFT JOIN folio_finance.budget    ON budget.fiscalyearid = fiscal_year.id
    LEFT JOIN folio_finance.fund      ON fund.id = budget.fundid
    LEFT JOIN folio_finance.fund_type ON fund_type.id = fund.fundtypeid
    LEFT JOIN folio_finance.ledger    ON ledger.id = fund.ledgerid;

Since this query occurs more frequently, the Reporting SIG has created a derived table for it. You can find the sql statement in the repository in the directory sql_metadb/derived_tables/ in the file finance_funds.sql.

[top]

2.2. Querying transactions and their linked invoices

A common use case is the viewing of transaction data and the associated invoices from the FOLIO app Invoices. A typical use case can be to see how amounts are recorded in system currency, if you used a foreign currency in the invoice. The linking is realized via UUIDs that link between the apps.

Example 1:

SELECT
    *
FROM
    folio_finance.transaction
    LEFT JOIN folio_invoice.invoices            ON invoices.id         = jsonb_extract_path_text(transaction.jsonb, 'sourceInvoiceId')::uuid 
    LEFT JOIN folio_invoice.invoice_lines       ON invoice_lines.id    = jsonb_extract_path_text(transaction.jsonb, 'sourceInvoiceLineId')::uuid 
    LEFT JOIN folio_finance.fund AS from_fund   ON from_fund.id        = transaction.fromfundid
    LEFT JOIN folio_finance.fund AS to_fund     ON to_fund.id          = transaction.tofundid 
    LEFT JOIN folio_finance.budget              ON budget.fundid       = transaction.fromfundid
                                               AND budget.fiscalyearid = transaction.fiscalyearid 
    LEFT JOIN folio_organizations.organizations ON organizations.id    = jsonb_extract_path_text(invoices.jsonb, 'vendorId')::uuid
WHERE 
    (
           jsonb_extract_path_text(transaction.jsonb, 'transactionType') = 'Pending payment'
        OR jsonb_extract_path_text(transaction.jsonb, 'transactionType') = 'Payment'
        OR jsonb_extract_path_text(transaction.jsonb, 'transactionType') = 'Credit'
    );

Since this query occurs more frequently, the Reporting SIG has created a derived table for it. You can find the sql statement in the repository in the directory sql_metadb/derived_tables/ in the file finance_transaction_invoices.sql.

Example 2:

SELECT 
    invoice_id,
    invoice_line_id,
    invoice_vendor_name,
    invoice_date,
    invoice_line_total,
    invoice_currency,
    invoice_exchange_rate,
    transaction_amount,
    transaction_currency,
    transaction_from_budget_name,
    transaction_from_fund_name
FROM 
    folio_derived.finance_transaction_invoices

In this example you can see that you only have to use the prepared sql statement from finance_transaction_invoices.sql. Here we selected only some attributes from this derived table.

[top]

2.2.1. Transaction amounts for credits

The transaction amounts for credits are always positive in the transaction table. If you want to use these for calculations, you must build a case distinction into your database query to get negative amounts. The following example shows how the calculation is carried out within the case distinction.

In addition, it may be necessary to merge the fund IDs from_fund_id and to_fund_id into one column. Especially for credits, the fund ID is stored under to_fund_id. This allows you to better use the UUID for funds.

Example:

SELECT
    transaction__t.id AS transaction_id,
    CASE WHEN transaction__t.transaction_type = 'Credit'
        THEN 
            transaction__t.to_fund_id
        ELSE
            transaction__t.from_fund_id
    END AS effective_fund_id,
    transaction__t.amount AS transaction_amount,
    CASE WHEN transaction__t.transaction_type = 'Credit'
        THEN 
            transaction__t.amount :: NUMERIC(19,2) * -1
        ELSE 
            transaction__t.amount :: NUMERIC(19,2)
    END AS effective_transaction_amount,
    transaction__t.currency AS transaction_currency,
    transaction__t.fiscal_year_id,
    transaction__t.source_invoice_line_id AS source_invoice_line_id,
    transaction__t.expense_class_id AS transaction_expense_class_id,
    expense_class__t.code AS expense_class_code,
    expense_class__t.name AS expense_class_name,
    expense_class__t.external_account_number_ext
FROM
    folio_finance.transaction__t
    LEFT JOIN folio_finance.expense_class__t ON expense_class__t.id = transaction__t.expense_class_id
WHERE 
    (
           transaction__t.transaction_type = 'Pending payment'
        OR transaction__t.transaction_type = 'Payment'
        OR transaction__t.transaction_type = 'Credit'
    )

[top]

2.3. Querying transactions and their linked purchase orders

Sometimes you want to see in the transactions which encumbrances were recorded in system currency for an order.

Example:

SELECT
    *
FROM
    folio_finance.transaction
    LEFT JOIN folio_orders.po_line              ON po_line.id          = jsonb_extract_path_text(transaction.jsonb, 'encumbrance', 'sourcePoLineId')::uuid 
    LEFT JOIN folio_orders.purchase_order       ON purchase_order.id   = jsonb_extract_path_text(transaction.jsonb, 'encumbrance', 'sourcePurchaseOrderId')::uuid 
    LEFT JOIN folio_finance.fund AS from_fund   ON from_fund.id        = transaction.fromfundid
    LEFT JOIN folio_finance.budget              ON budget.fundid       = transaction.fromfundid
                                               AND budget.fiscalyearid = transaction.fiscalyearid 
    LEFT JOIN folio_organizations.organizations ON organizations.id    = jsonb_extract_path_text(purchase_order.jsonb, 'vendor')::uuid
WHERE
    jsonb_extract_path_text(transaction.jsonb, 'transactionType') = 'Encumbrance';

Since this query occurs more frequently, the Reporting SIG has created a derived table for it. You can find the sql statement in the repository in the directory sql_metadb/derived_tables/ in the file finance_transaction_purchase_order.sql.

[top]

3. Relations between purchase order and invoice

3.1. Purchase order line and invoice line

There is a connection between an order line and an invoice line via a UUID.

Example:

SELECT 
    *
FROM 
    folio_invoice.invoice_lines
    LEFT JOIN folio_orders.po_line ON po_line.id = jsonb_extract_path_text(invoice_lines.jsonb, 'poLineId')::UUID 

Metadb offers so-called transformed tables that represent the content of JSON objects relationally. The same query with transformed tables would look like this. However, the result is a relational representation.

Example with transformed tables:

SELECT 
    *
FROM 
    folio_invoice.invoice_lines__t
    LEFT JOIN folio_orders.po_line__t ON po_line__t.id = invoice_lines__t.po_line_id 

Please ensure that you select the attributes sensibly, otherwise a cross product could occur.

[top]

3.2. Purchase order and invoice

There is a table in the schema orders with the name order_invoice_relationship. In this table you can find the direct connection between a order and an invoice.

Example JSON object:

{
  "id": "963a6c4f-7244-4ee5-b33f-e0f54e38c7bc",
  "invoiceId": "ee709d6b-af8a-4b8a-8293-295f8fbffb94",
  "purchaseOrderId": "74fac919-fa47-4146-ad92-039a4945b56a"
}

Example:

SELECT
    * 
FROM 
    folio_orders.order_invoice_relationship__t

You can use these UUIDs for the connection. However, you should know that the individual connections between the order line and invoice line will be lost.

[top]

4. Vouchers

The typical payment chain also exists in FOLIO: Invoice > Transaction > Voucher. This means you have an invoice that you pay. The moment you approve the invoice, a transaction is created. Later you have to transfer the invoice to pay status. After the transaction the system creates a voucher with voucher lines. The following example shows how to query the information about the vouchers. In this example we first look at the voucher lines and connect them to the record for the voucher.

Example:

SELECT 
    voucher_lines.id AS voucher_line_id,
    jsonb_extract_path_text(fund_distributions.jsonb, 'value') :: NUMERIC(19,2) AS voucher_line_value,
    jsonb_extract_path_text(fund_distributions.jsonb, 'distributionType') AS voucher_line_distribution_type,
    jsonb_extract_path_text(fund_distributions.jsonb, 'fundId') :: UUID AS fund_id,
    jsonb_extract_path_text(fund_distributions.jsonb, 'code') AS voucher_line_fund_code, 
    jsonb_extract_path_text(fund_distributions.jsonb, 'expenseClassId') :: UUID AS voucher_line_expense_class_id,
    jsonb_extract_path_text(fund_distributions.jsonb, 'encumbrance') :: UUID AS voucher_line_encumbrance_id,	
    jsonb_extract_path_text(fund_distributions.jsonb, 'invoiceLineId') :: UUID AS invoice_line_id,
    jsonb_extract_path_text(voucher_lines.jsonb, 'externalAccountNumber') AS external_account_number,
    vouchers.id AS voucher_id,
    jsonb_extract_path_text(vouchers.jsonb, 'voucherDate') :: TIMESTAMPTZ AS voucher_voucher_date,
    jsonb_extract_path_text(vouchers.jsonb, 'voucherNumber') :: INTEGER AS voucher_voucher_number,
    jsonb_extract_path_text(vouchers.jsonb, 'status') AS voucher_status,
    jsonb_extract_path_text(vouchers.jsonb, 'amount') :: NUMERIC(19,2) AS voucher_total_amount,
    jsonb_extract_path_text(vouchers.jsonb, 'systemCurrency') AS voucher_system_currency,
    jsonb_extract_path_text(vouchers.jsonb, 'invoiceCurrency') AS voucher_invoice_currency,
    jsonb_extract_path_text(vouchers.jsonb, 'exchangeRate') :: NUMERIC(19,14) AS voucher_exchange_rate,
    jsonb_extract_path_text(vouchers.jsonb, 'vendorId') :: UUID AS voucher_vendor_id,
    jsonb_extract_path_text(vouchers.jsonb, 'invoiceId') :: UUID AS voucher_invoice_id,    
    jsonb_extract_path_text(vouchers.jsonb, 'batchGroupId') :: UUID AS voucher_batch_group_id,          
    COALESCE(jsonb_extract_path_text(vouchers.jsonb, 'exportToAccounting') :: BOOLEAN, FALSE) AS voucher_export_to_accounting,
    COALESCE(jsonb_extract_path_text(vouchers.jsonb, 'enclosureNeeded') :: BOOLEAN, FALSE) AS voucher_enclosure_needed
FROM 
    folio_invoice.voucher_lines
    CROSS JOIN LATERAL jsonb_array_elements(jsonb_extract_path(voucher_lines.jsonb, 'fundDistributions')) WITH ORDINALITY AS fund_distributions (jsonb)
    LEFT JOIN folio_invoice.vouchers ON vouchers.id = jsonb_extract_path_text(voucher_lines.jsonb, 'voucherId') :: UUID

If you want to connect the voucher lines with their invoice lines, please note that you need several UUIDs to create a meaningful connection.

You need the UUIDs:

  • invoiceLineId
  • fundId
  • expenseClassId

The JOIN to the invoice line takes place in a single line with several UUIDs that are linked with an AND. Additionally, you must note that the expense class id is optional. For this reason you need a UNION when querying data.

Example:

WITH invoice_lines AS (
    SELECT
        invoice_lines.id AS invoice_line_id,
        jsonb_extract_path_text(fund_distributions.jsonb, 'fundId') :: UUID AS fund_id,
        jsonb_extract_path_text(fund_distributions.jsonb, 'expenseClassId') :: UUID AS expense_class_id
    FROM
        folio_invoice.invoice_lines
        CROSS JOIN LATERAL jsonb_array_elements(jsonb_extract_path(invoice_lines.jsonb, 'fundDistributions')) WITH ORDINALITY AS fund_distributions (jsonb)
)
SELECT
    *
FROM
    folio_invoice.voucher_lines
    CROSS JOIN LATERAL jsonb_array_elements(jsonb_extract_path(voucher_lines.jsonb, 'fundDistributions')) WITH ORDINALITY AS fund_distributions (jsonb)
    LEFT JOIN folio_invoice.vouchers ON vouchers.id = jsonb_extract_path_text(voucher_lines.jsonb, 'voucherId') :: UUID
    LEFT JOIN invoice_lines ON invoice_lines.invoice_line_id = jsonb_extract_path_text(fund_distributions.jsonb, 'invoiceLineId') :: UUID
        AND invoice_lines.fund_id = jsonb_extract_path_text(fund_distributions.jsonb, 'fundId') :: UUID
WHERE 
    jsonb_extract_path_text(fund_distributions.jsonb, 'expenseClassId') IS NULL 
UNION 
SELECT
    *
FROM
    folio_invoice.voucher_lines
    CROSS JOIN LATERAL jsonb_array_elements(jsonb_extract_path(voucher_lines.jsonb, 'fundDistributions')) WITH ORDINALITY AS fund_distributions (jsonb)
    LEFT JOIN folio_invoice.vouchers ON vouchers.id = jsonb_extract_path_text(voucher_lines.jsonb, 'voucherId') :: UUID
    LEFT JOIN invoice_lines ON invoice_lines.invoice_line_id = jsonb_extract_path_text(fund_distributions.jsonb, 'invoiceLineId') :: UUID
        AND invoice_lines.fund_id = jsonb_extract_path_text(fund_distributions.jsonb, 'fundId') :: UUID
        AND invoice_lines.expense_class_id = jsonb_extract_path_text(fund_distributions.jsonb, 'expenseClassId') :: UUID
WHERE 
    jsonb_extract_path_text(fund_distributions.jsonb, 'expenseClassId') IS NOT NULL 

[top]

5. Organizations

5.1. Purchase orders and organizations

In Orders you can link to organizations depending on their role for the order.

There are 3 common use cases where you can link to:

  • Vendor informations are stored in the Organizations app
  • "Ship to" addresses are stored in the settings from FOLIO
  • "Bill to" addresses are stored in the settings from FOLIO

Example:

SELECT 
*
FROM 
    folio_orders.purchase_order__t
    LEFT JOIN folio_organizations.organizations__t AS org_vendor ON org_vendor.id = purchase_order__t.vendor
    LEFT JOIN folio_configuration.config_data__t AS ship_to ON ship_to.id = purchase_order__t.ship_to
    LEFT JOIN folio_configuration.config_data__t AS bill_to ON bill_to.id = purchase_order__t.bill_to

[top]

5.2. Invoices and organizations

In Invoices you can link to organizations depending on their role for the invoice.

There are 2 common use cases where you can link to:

  • Vendor informations are stored in the Organizations app
  • "Bill to" addresses are stored in the settings from FOLIO

Example:

SELECT 
    *
FROM 
    folio_invoice.invoices__t
    LEFT JOIN folio_invoice.invoices ON invoices.id = invoices__t.id
    LEFT JOIN folio_organizations.organizations__t AS org_vendor ON org_vendor.id = invoices__t.vendor_id
    LEFT JOIN folio_configuration.config_data__t AS org_bill_to ON org_bill_to.id = jsonb_extract_path_text(invoices.jsonb, 'billTo') :: UUID 

[top]