Skip to end of metadata
Go to start of metadata

Introduction

The current hub of CiviCRM's transaction model is the Contribution, which is essentially a collection of line items being "purchased" at one time by the end user, for example, an event ticket, additional fee for a session, and an additional donation. Currently, contributions have a one to one relationship with payments (or one to zero in case of a contribution that is not yet complete). This means that you cannot easily combine different types of income into a Contribution (each Contribution has a single contribution type for all line items), and you cannot make partial payments towards individual line items in the Contribution, and you cannot make a single payment against multiple 'contributions'. 

To minimise the amount of code changes initially required, while providing greater flexibility and extensibility to collect line items into a Contribution without each of them having to necessarily be considered all of the same type, this project will leave most of the existing data and code as is, replicating just the financial information for items in a civicrm_financial_item table, whether each item is an individual donation, event fee, membership dues, or some future item that can be "purchased" through an extension. With this change, the creation of a Contribution will include the creation of records in several tables: in civicrm_contribution, a record of the contribution itself; in civicrm_line_item, details about the contribution, participant tickets or memberships; in civicrm_financial_item, a record of the financial aspects of these line items. 

Terminology note: Other parts of this project documentation reference the concept of an "Order", which is another name for what is here called a Contribution.

General Concepts

Financial items and the civicrm_line_item table

Currently CiviCRM stores the component items of a contribution in the civicrm_line_item table, which includes data on the item itself, unit price, quantity, and domain-specific data such as participant count (when the line item is based on a price set field that has a participant count).  This project will add a table civicrm_financial_item, which will contain one record for every record in civicrm_line_item, as well as records for other financial items such as transfers between accounts.  In other words, line items will be a sub-set of financial items; and in fact we expect almost all financial items to be line items as well (at least when they are income, but perhaps in future for an expense too).

Financial items and the civicrm_financial_item table

A financial item is an item that is included in a Contribution that causes the end user to incur a financial charge of a certain amount.  Examples include donations, event fees, membership dues and other similar items. All are separate financial items and are accounted for separately.

The core of any financial item is a date that it is incurred; the contact_id of the "purchaser" contact for the transaction; the contact_id of the "supplier" contact for the transaction; a human readable description for the financial item that can be displayed where appropriate; an amount; a currency; a financial_account_id to identify what financial_account the transaction should be recorded to; a source_table to advise where to find additional attributes for the financial_item; a source_id to link to the specific row in the source_table that contains the additional attributes for this specific financial_item, and a status (an integer linking to an Option List item, that provides the payment status of that item).

It is intended that by ensuring all types of financial_item are stored in the civicrm_financial_item table, code and SQL queries can be written and reused for a range of different financial transactions, and it will be possible when dealing with any financial_item to find what other financial_items are dependent on or connected to it (or vice versa), so that actions on one item can flow on to the other associated items.

Payments are linked in whole or in part to any number of different financial_items through the civicrm_entity_financial_trxn table, which records a link between the financial_trxn table that currently stores payments made through payment processors (but which after this project will be used to store all payments), and the relevant financial_item/s being paid.

Items in a Contribution

Donation

Terminology note: Because the term Contribution is used to mean the entire collection of line items, it is not also used to mean a single line item representing an outright financial donation. This type of financial item is here called a Donation.

An outright financial Donation is recorded with single rows in civicrm_contribution, civicrm_line_item, civicrm_price_set_entity (which provides a convenient link from a contribution to the line_items associated with it) and civicrm_financial_item. The source_table field in the civicrm_financial_item row will contain "civicrm_line_item" and the entity_id will contain the id of the row in that table.

Membership

A membership is recorded with single rows in civicrm_membership, civicrm_financial_item, civicrm_line_item, and civicrm_price_set_entity.  The source_table field in the civicrm_financial_item row will contain "civicrm_line_item" and the entity_id will contain the id of the row in that table.

Event registration (Participant)

An event participation is recorded with a single row in civicrm_participant and a single row in civicrm_financial_item.  The source_table field in the civicrm_financial_item row will contain "civicrm_line_item" and the entity_id will contain the id of the row in that table.

Pledge

When a person "orders" a pledge, the pledge is recorded in the existing tables separate from the financial_item table, with a financial_item only created when a pledge falls due.

In future the better approach may be to create a "Pledge order" whereby the contact's order is a batch made up of each of the individual pledge amounts to be paid at future dates, linked to individual items for each future pledge payment in either the contribution_item table or in a new civicrm_pledge_item table if different information is required for a pledge_item than for a contribution_item.

Batches

At time of writing, batches, which were previously proposed as a model for recording Contributions, are now to be used only as described here: CiviAccounts Specifications - Batches

Changes, Reversals, Refunds

This document does not yet address the application of changes, reversals, or refunds to Contributions and to specific financial_items within a Contribution. See CiviAccounts Specifications-Changes, Reversals, Refunds for more information.

Technical Specification

Alert

Any items in this color need review before implementing.

Schema

  1. Create a new table civicrm_financial_item with the following specifications:
    create table `civicrm_financial_items` (
    `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'unique id' ,
    `created_date` DATETIME NOT NULL COMMENT 'Date and time the item was created',
    `transaction_date` DATETIME NOT NULL COMMENT 'Date and time of the source transaction',
    `contact_id` INT(10) UNSIGNED NOT NULL COMMENT 'FK to Contact ID of contact the item is from' ,
    `description` VARCHAR(255) NULL COMMENT 'Human readable description of this item, to ease display without lookup of source item' ,
    `amount` DECIMAL(20,2) NOT NULL COMMENT 'Total amount of this item',
    `currency` VARCHAR(3) NULL COMMENT 'Currency for the amount',
    `financial_account_id` INT(10) UNSIGNED NULL COMMENT 'FK to civicrm_financial_account',
    `status_id` INT(10) UNSIGNED NULL COMMENT 'Payment status: test, paid, part_paid, unpaid (if empty assume unpaid)' ,
    `entity_table` VARCHAR(64) NULL COMMENT 'The table providing the source of this item such as civicrm_line_item' ,
    `entity_id` INT(10) UNSIGNED NULL COMMENT 'The specific source item that is responsible for the creation of this financial_item' ,
    PRIMARY KEY (`id`) ,
    INDEX `IX_created_date` (`created_date` DESC) ,
     INDEX `IX_transaction_date` (`transaction_date` DESC) ,
     INDEX `IX_source` (`source_table` ASC, `source_id` ASC) ,
    INDEX `FK_financial_item_contact_id` (`contact_id` ASC) ,
    INDEX `FK_financial_item_contact_id_to` (`contact_id_to` ASC) ,
    CONSTRAINT `FK_financial_item_contact_id`
    FOREIGN KEY (`contact_id` )
    REFERENCES `civicrm_contact` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
    CONSTRAINT `FK_financial_item_contact_id_to`
    FOREIGN KEY (`contact_id_to` )
    REFERENCES `civicrm_contact` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
    ENGINE = InnoDB
    COMMENT = 'Financial data for civicrm_line_item, etc.'
  2. Create a new record in civicrm_option_group for financial_item_status
  3. For the financial_item_status option group, create records in civicrm_option_value for 'Paid', 'Partially paid', and 'Unpaid'

    NB: following 4, 5 and 6 have been completed.

  4. In the table civicrm_price_field_value, add column `financial_type_id` as a foreign key to civicrm_financial_type.id 
  5. In the table civicrm_price_field_value, add column `deductible_amount` DECIMAL(20,2) NOT NULL COMMENT 'Tax-deductible portion of the amount'

  6. In the table civicrm_line_item, add column `deductible_amount` DECIMAL(20,2) NOT NULL COMMENT 'Tax-deductible portion of the amount', 

  7. In civicrm_payment_processor, add column `financial_type_id` int(10) NOT NULL as a foreign key to civicrm_financial_type.id.
  8. Upgrade script:
    1. We will integrate our work here into the upgrade after the upgrade to price_sets has taken place for all historical transactions. When there is a reference in this section to a contribution page, it should be understood to include both event signup pages and contribution pages and contribution pages including memberships.
    2. For price fields that are in price sets that are used on a single page or multiple pages with the same contribution_type_id for all pages, use the contribution_type_id of the contribution_page to determine the appropriate financial_type_id for each civicrm_price_field_value record via the processor used to upgrade contribution types to financial_accounts and financial_types.
    3. For price fields that are in price sets that are used on multiple pages with different contribution_type_id's, replicate the price set for each contribution_type_id, and set up the financial_type_id for each one using the algorithm in b).
    4. For price fields that are in price sets that are not assigned to a contribution_page, use the kind of price set (contribution, event, membership) to derive a default financial_type_id from the reserved financial_type_id's for Donations, Events, and Membership Fees.
    5. Use the algorithms in b) and c) to determine the deductible_amount for civicrm_price_field_value from the deductible amount in the civicrm_contribution record. (Please outline cases here where that is not possible, perhaps e.g. when the contribution is for a combined donation and membership payment. In these cases set the deductible amount to 0.) Set it to 0 for cases identified by d).
    6. For each civicrm_payment_processor, create a financial_type and insert its id in civicrm_payment_processor.financial_type_id. For each of these financial_types, create a financial_account with financial_account_type of 'Asset'. Link the financial_account with the financial_type by inserting a civicrm_entity_financial_account record with account_relationship of 'is asset account of' (or equivalent - I don't recall the enum values). Use the following values:
      1. civicrm_financial_type.name: civicrm_payment_processor.name
      2. civicrm_financial_type.description: civicrm_payment_processor.description
      3. civicrm_financial_type.is_deductible: '0'
      4. civicrm_financial_type.is_reserved: '0'
      5. civicrm_financial_type.is_active: civicrm_payment_processor.is_active
      6. civicrm_financial_type.timestamp: now()
      7. civicrm_financial_account.name: civicrm_payment_processor.name
      8. civicrm_financial_account.description: civicrm_payment_processor.description
      9. civicrm_financial_account.contact_id: tbd - ask dgg how best to derive contact_id of organization implementing CiviCRM
      10. civicrm_financial_account.financial_account_type_id: based on 'is asset account of'
      11. civicrm_financial_account.account_code, parent_id and tax_rate are all null
      12. civicrm_financial_account.is_header_account, is_deductible, is_reserved, is_tax all '0'
      13. civicrm_financial_account.is_active: civicrm_payment_processor.is_active
      14. civicrm_financial_account.is_default: civicrm_payment_processor.is_default (nb: check this works and we have not elsewhere created a default asset account during upgrade)
      15. civicrm_entity_financial_account.entity_table: 'civicrm_financial_type'
      16. civicrm_entity_financial_account.entity_id: civicrm_financial_type.id of inserted record
      17. civicrm_entity_financial_account.account_relationship: 'is asset account of' or equivalent
      18. civicrm_entity_financial_account.financial_account_id: civicrm_financial_account.id of just inserted record
  9. In the UI for creating and editing a payment processor, provide the option of selecting an existing financial_type from existing one. The financial_type must have a 'is asset account of' relationship with a financial account record with a financial_account_type of 'asset' in order to be validated. Ensure that a financial_type associated with a payment processor never loses having an account with this relationship.
  10. Create a fifth default record in civicrm_financial_account for Banking Fees, with a financial_account_type of expense, is_default=true, and a null value for the accounting_code.

 

Class structure and functionality

Ensure the following functionality in BAO classes:

Hooks

hook_civicrm_pre($op, $objectName, $id, &$params)

Before operations 'view', 'create', 'edit', and 'delete', invoke this hook for each financial_item, with these values:

  • $op: 'view', 'create', 'edit', or 'delete'
  • $objectName: 'FinancialItem'
  • $id: NULL
  • $params: all values to be stored in civicrm_financial_item (including line_item_id, which should have been created at this point)

hook_civicrm_post($op, $objectName, $objectId, &$objectRef)

After operations 'view', 'create', 'edit', and 'delete', invoke this hook for each financial_item, with these values:

  • $op: 'view', 'create', 'edit', or 'delete'
  • $objectName: 'financialItem'
  • $id: the value of civicrm_financial_item.id
  • $objectRef: the FinancialItem DAO object

Forms

Currently this specification does not attempt to define any changes to existing forms.

Code changes

Changes will be made to the code in relation to both the creation of the "obligation" (Financial Item) and "payment to satisfy the obligation" (Financial Transaction).  A Financial Transaction will also be recorded for the "Accounts Receivable", which are the asset created by the unpaid obligations.

Recording the Obligation - Line Item / Financial Item (Income)

When a contribution is made, at the point where civicrm_line_items are being created, one row will be inserted into civicrm_financial_item for each line item as follows:

  1. created_date: (today's date)
  2. transaction_date: (date of contribution the civicrm_line_item is in)
  3. contact_id: civicrm_contribution.contact_id
  4. description: (civicrm_line_item.qty<>1 ? civicrm_line_item.qty . ' of ' : '') . content of this item's label as displayed in the contribution form)
  5. amount: civicrm_line_item.line_total
  6. currency: (the value of civicrm_contribution_page.currency for the page where the contribution came from)
  7. financial_account_id: search civicrm_entity_financial_account for entity_table='civicrm_financial_type' and entity_id=civicrm_line_item.financial_type_id and account_relationship='Income Account is', and return financial_account_id (I understand the financial_type_id has/is being added to the line_item table and is recorded there based on the price_field_value at time of creating the line_item, so any later change in the price_field_value table does not cause a loss of data integrity)
  8. status_id: (the value of civicrm_option_value.value where the option_group is 'financial_item_status' and the option value name is "Unpaid")
  9. source_table: 'civicrm_line_item'
  10. source_id: (the value of civicrm_line_item.id for the line_item record)
  11. Other fields are left at their default values.

Recording an initial transaction with or without a payment 

  1. Insert a record in civicrm_financial_trxn for each contribution, event, and membership transaction, setting civicrm_financial_trxn.to_financial_account_id as follows:
    1. If the transaction is just pay_later, then select the to_financial_account_id using the financial_type and civicrm_entity_financial_account.account_relationship of accounts receivable.
    2. If the transaction is immediate payment of the full amount, then select the to_financial_account_id using the civicrm_payment_processor.financial_type_id and civicrm_entity_financial_account.account_relationship of asset.
    3. If the payment is a manual payment for the full amount by Cash/Check, then
      1. if there is only one financial account of financial_type=asset, then use it
      2. if there is more than one financial_account of financial_type=asset, then an additional field will be displayed to the admin user just under Contribution Type / Financial Account allowing them to select "Payment Received Into" (ie to_financial_account_id) with a drop down list of all financial_accounts of financial_type=asset, with the default selection being the one with is_default=true.
    4. If the payment is for part of the transaction amount, then create one entry as above in 1a for the pay later amount, and another entry in civicrm_financial_trxn for the rest of the amount as per 1b or 1c, whichever is appropriate.
  2. Create a row in civicrm_entity_financial_trxn between the financial_trxn and each financial_item in the transaction:
    1. Set civicrm_entity_financial_trxn.amount to the amount specified in the UI for this item (see "Technical Specification: Calculating paid amount of items in initial payment" in CiviAccounts Specifications-Flexible User Payments).
      1. However, reject the transaction with an error if the total of all civicrm_entity_financial_trxn.amount values for this item is either 
        1. more than the amount of the item itself (i.e., don't allow the item to be over-paid), or 
        2. less than zero.
    2. Calculate ITEM_TOTAL as the total of all civicrm_entity_financial_trxn.amount values for this item, and set civicrm_financial_item.status_id as follows:
      1. If ITEM_TOTAL is 0, set to the value of civicrm_option_value.value where the option_group is 'financial_item_status' and the option value name is "Unpaid"
      2. If ITEM_TOTAL is equal civicrm_financial_item.amount, set to the value of civicrm_option_value.value where the option_group is 'financial_item_status' and the option value name is "Paid"
      3. If ITEM_TOTAL is less than civicrm_financial_item.amount, set to the value of civicrm_option_value.value where the option_group is 'financial_item_status' and the option value name is "Partially paid"
  3. Create upgrade code that translates existing civicrm_financial_trxn records linked to civicrm_contribution records so that they link to civicrm_line_items for those civicrm_contribution records.

Recording Fees

If the financial_item is being paid through a payment processor and a fee_amount is charged, or if the contribution is being manually recorded and a fee is recorded, then create an additional civicrm_financial_item for the fee as follows:

  1. Link this civicrm_financial_item to the civicrm_financial_trxn using both through the civicrm_financial_item.entity_table and civicrm_financial_item.entity_id (this indicates what the fee is about) and through civicrm_entity_financial_trxn (this shows the the civicrm_financial_item has been paid, and that the payment that caused fee actually paid for the fee):
    1. civicrm_financial_item.entity_table = 'civicrm_financial_trxn'
    2. civicrm_financial_item.entity_id = the new civicrm_financial_trxn.id
    3. civicrm_entity_financial_trxn.entity_table = 'civicrm_financial_item'
    4. civicrm_entity_financial_trxn.entity_id = the new civicrm_financial_item.id
  2. For both manual and payment processor cases, use the to_financial_account_id for the payment identified in "Recording an initial transaction with or without a payment", point 1 as the from_financial_account_id for the fee.
  3. Select the to_financial_account_id as follows:
    1. For a payment_processor, use the financial_type and civicrm_entity_financial_account.account_relationship of expense. If none exists, raise an error. (We don't want to force this at config time since some payment processors may not report fees back).
    2. For a manual payment,
      1. if there is only one financial account of financial_account_type=expense, then use it
      2. if there is more than one financial_account of financial_type=expense, then an additional field will be displayed to the admin user in the Additional Details fieldset just under Fee Amount allowing them to select "Fee Account" with a drop down list of all financial_accounts of financial_type=expense, with the default selection being the one with is_default=true.
  4. status_id: Option List id for Paid (on the basis it has already been taken out of the trxn and therefore the financial_trxn.net_amount is exclusive of the fee).
  5. set civicrm_financial_item.contact_id to the domain or system owner contact id (see CRM-9897).

Recording Pay Later Payments

For the payment of a previous unpaid/part paid financial_item (which will currently come from a contribution/line_item, but thanks to the description being stored in the financial_item, even obligations coming from third party forms/drupal modules/joomla extensions/web services can be displayed for payment through CiviCRM):

  1. check the total payment matches the total of the individual financial_item amounts 
  2. check that the financial_items the payment is for have status unpaid or part paid, and
  3. if the financial_item is part paid, use the entity_financial_trxn table to locate the transactions partially paying that financial_item and ensure that the current payment combined with the sum of the financial_trxn using the "Accounts Receivable Payment Processor" for that financial_item, will not exceed the value of the financial_item,
  4. record an equal negative financial_trxn against the account_id for the financial_account used by the "Accounts Receivable Payment Processor" for that financial_item previously, to pay out the account receivable, that is:
    1. from_financial_account_id is the accounts receivable account
    2. to_financial_account_id is the asset account
    3. the amount of the payment
  5. link the new financial_trxn to the financial_trxn for the payment via the entity_financial_trxn table.

This process will create records that can be synced with a financial accounts package whether they use a cash or an accruals system of accounting, with the financial_item table recording changes in the "Profit and Loss accounts" (Income and Expenses), while the financial_trxn table records changes in the "Balance Sheet" (Assets and Liabilities).

Changes/Reversals

There are two different categories of changes/reversals. Changes/Reversals to:

  1. Financial Items (ie edits to contributions, memberships and event purchases); and
  2. Financial Transactions (eg edits to payments)

The UI may allow the user to change an existing transaction and simultaneously make a payment or refund for some or all of the amount at the same time. We will spec out what happens for the line items and financial items first, then move on to the financial transactions second. However, every accounting transaction must be balanced, so there is some interaction between the two - the total difference in the financial items has to be reflected in a corresponding entry in the civicrm_financial_trxn table.

Changes/Reversals to Line Items / Financial Items

For each line item being saved after an edit where an attempt is being made to change/reverse a line-item:

  1. There may have been a change either to the financial_type for the line item that results in a new income account, or the income account within the existing financial type may have changed (though it is possible that a change in financial_type has not resulted in a change of income account). If there is a change to the line_item.financial_type_id, check via entity_financial_account.account_relationship for the "Income account is" to see if that account is different than the existing financial_item.financial_account_id. If changed, get confirmation from user, create a transaction that move the original amount from the original income account to the new income account, and proceed with rest of transaction.
    1. More specifically, if entity_financial_account.financial_account_id != financial_item.account_id, display a confirmation dialog that "The Income Account for the <line item label> has been changed from <original account name> to <new account name>. Clicking OK will move this income from the old to the new income account."
    2. If Cancel, return to edit form.
    3. If OK,
      1. create a new financial_item record the same as the old one with the new financial_account_id
      2. create a financial_trxn record with from_financial_account_id=old financial_item.account_id and to_financial_account_id=new financial_item.account_id, with amount=old financial_item.amount.
      3. create two entity_financial_trxn records connecting the new financial_trxn record to both the old and the new financial_item records
  2. If the change would result in the total of the financial_item for the line_item becoming less than zero, throw an exception and don't proceed
  3. Create a new financial_item with source_table and source_id set to the civicrm_line_item with the amount of the adjustment against the account
    1. E.g. if the original amount is 100 and the new amount is 50, then the adjustment is -50; if the original is 100 and the new amount is 125, then the adjustment is 25.
  4. If the change is negative (credit/refund)
    1. if the financial_item was previously unpaid or part paid, if change means the original financial_item is now fully paid or overpaid, set its status to paid
    2. if the financial_item was previously paid, set the status to part paid or unpaid as appropriate
  5. else if the change is positive (increase in revenue)
    1. if the financial_item was previously unpaid or partially paid, don't change the status of the financial_item
    2. if the financial_item was previously paid, change the status to partially paid
  6. else the change is zero and nothing needs to be done. This may be the case where the account is being changed.

Items 4 - 6 above attempt to keep payment status of line items closely related to their previous status rather than deal with this for all financial items at the overall contribution record level.

Store any changes in the total amount owing for the whole price set/transaction as follows:

  1. Determine the total_difference_amount for the whole price set/transaction including any payment or refund, ie the change in the amount of the obligation: Sum of line items in current version of price set that is about to be stored minus the sum of the line items of the previous version of price set that is currently in the db.
  2. Determine the total_amount_previously_owing by adding or subtracting financial_trxn amounts for the transaction as follows:
    1. Asset: subtract (this means a deposit has been received)
    2. Liability: add (this means a payment such as a refund has been made)
    3. Accounts receivable: add (this records what is owed eg for a pay later)
    4. Accounts payable: subtract (this records what has to be paid, eg a refund that will be paid via cheque to be cut later)
    5. Revenue: add (this is the obligation of a donor, and appears in this field if an accounts receivable is being cancelled)
    6. Expense: subtract (this supports recording what is owed to a grant winner, and appears in this field if an accounts payable is being cancelled)
  3. Calculate total_amount_currently_owing = total_amount_previously_owing + total_difference_amount - payment.amount + refund.amount
  4. Record any payment being made at the time of the transaction as one, two or perhaps three financial_trxn records: against a/r amount, a regular payment, and potentially an overpayment:
    1. if total_amount_previously_owing > 0 and total_amount_currently_owing < total_amount_previously owing, (let's pay down the account receivable)
      1. if total_amount_currently_owing > 0
      2. -----
      3. if payment.amount <= total_amount_previously_owing, create one financial_trxn with from_financial_account_id = a/r account and to_financial_account_id = the asset account for the payment and financial_trxn.amount=payment.amount.
      4. if payment.amount > total_amount_previously_owing, create one financial_trxn with from_financial_account_id = a/r account and to_financial_account_id = the asset account for the payment and financial_trxn.amount=payment.amount and a second financial_trxn with from_financial_account_id = null, to_financial_account_id = the asset account for the payment and financial_trxn.amount=payment.amount - total_amount_previously_owing
    2. if total_amount_previously_owing >= 0, then record payment in financial_trxn with from_financial_account_id blank
  5. Record any refund being made at the time of the transaction as one or perhaps two financial_trxn records: against a/p amount, a regular refund, and an inappropriately large refund that creates an a/r to be paid later:
    1. if total_amount_previously_owing < 0, then
      1. if refund.amount <= - total_amount_previously_owing, create one financial_trxn with from_financial_account_id = a/p account and to_financial_account_id = the asset account for the refund and financial_trxn.amount=refund.amount
      2. if refund.amount > - total_amount_previously_owing, create one financial_trxn with from_financial_account_id = a/p account and to_financial_account_id = the asset account for the refund and financial_trxn.amount=refund.amount and a second financial_trxn with from_financial_account_id = null, to_financial_account_id = the asset account for the refund and financial_trxn.amount= total_amount_previously_owing - refund.amount
  6. Create entity_financial_item records linking financial_trxn to the financial_item records that have been created for the change transaction.
    1. The allocation of the amount of the financial_trxn should be determined by the choices of the user through the UI as per other payments.

 

  1. If this total_difference_amount is a positive number, then more money is owing from contributor. Create a new financial_trxn record for the adjustment amount. Depending on invoking context and adjustment, this may be:
    1. a reduction of a refund owing amount,
    2. the elimination of a refund owing amount,
    3. a pay later a/r transaction,
    4. a pay immediate asset transaction for an online payment or,
    5. a pay immediate manually recorded payment entered at same time as changes
    It is possible for b) to be combined with c, d, or e. Store a new transaction for the appropriate case in civicrm_financial_trxn as per 1 of 'Recording an initial transaction with or without a payment' above.
  2. If the result is 0, then continue / do nothing
  3. If the result is a negative number, then either less money is owing from the contributor, and / or a refund is owed to the contributor. Figure out current amount owing on whole transaction as follows:
    1. find all civicrm_financial_trxn records related to the contribution
      1. Select financial_trxn_id from civicrm_entity_financial_trxn for entity_table='civicrm_financial_item' and entity_id in (select id from civicrm_financial_item fi inner join civicrm_line_item li on fi.entity_id=li.id and fi.entity_table='civicrm_line_item' where li.contribution_id=this->contribution_id)
    2. if total_amount_owing < 0 then a refund is owed
      1. Mark the status of all financial_items and the contribution as paid
    3. If total_amount_owing >= 0, then
      1. civicrm_financial_trxn.total_amount = total_amount_owing + total_difference_amount (which is a negative amount) 
      2. if civicrm_financial_trxn.total_amount < 0 then
      3. // we need to offset the existing civicrm_financial_trxn for accounts receivables accounts for this transaction
      4. for each civicrm_financial_trxn for accounts receivables accounts for this transaction
      5. civicrm_financial_trxn.total_amount = - total_amount_owing, to_financial_account_id = the
      6. create another new civicrm_financial_trxn record for a refund with civicrm_financial_trxn.total_amount = total_amount_owing - total_difference_amount, setting the to_financial_account_id to the
      7. if (to_distribute_amount < 0) then
      8. // pay all the owing amounts
      9. for each line_item
        1. create a civicrm_entity_financial_trxn record linking the
        2. set status to paid
      10. if (to_dis

Use that amount to change the contribution.status if necessary (if old status paid and adjustment_total>0, then new status=partly paid; if old_status=partly paid and < some calcuation to be determined using old_total and adjustment_total>, set new status=paid)

Create a new financial_trxn record for the adjustment amount. Depending on invoking context and adjustment, this may be:

  • a pay later a/r transaction
  • a pay immediate asset transaction
  • a refund immediate asset transaction
  • a refund later a/p transaction

Changes/Reversals to Financial Transactions (Payments)

This section will deal with changes to payments.

TBD

Labels
  • None

Creative Commons License
Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution-Share Alike 3.0 United States Licence.