Skip to content

FOSFA-473: Add Functionality To Import Credit Note And Credit Note Allocations#274

Open
shahrukh-compuco wants to merge 1 commit into
masterfrom
fosfa-473-add-credit-note-import-functionality
Open

FOSFA-473: Add Functionality To Import Credit Note And Credit Note Allocations#274
shahrukh-compuco wants to merge 1 commit into
masterfrom
fosfa-473-add-credit-note-import-functionality

Conversation

@shahrukh-compuco
Copy link
Copy Markdown
Contributor

Overview

Adds bulk-import support for Credit Notes and Credit Note Allocations (entities provided by io.compuco.financeextras) via the nz.co.fuzion.csvimport extension. Until now both entities could only be created one at a time through the on-screen forms or APIv4 — there was no way to feed a CSV file in.

Two new APIv3 entities are introduced so csvimport can pick them up automatically:

  • CreditNoteAllocation — a thin APIv3 wrapper over the existing BAO. One CSV row = one allocation.
  • CreditNoteImporter — a fake APIv3 entity dedicated to credit-note imports. One CSV row = one credit-note line; rows that share a credit_note_external_id are grouped into a single credit note with multiple lines (mirroring the membershipextrasimporterapi pattern). All accounting entries (financial transaction, financial items, entity-financial-trxn links) are produced exactly the way the APIv4 CreditNote.save action produces them.

Includes pre-write validation, friendly date handling, automatic tax derivation from the financial type's Sales Tax Account, and a per-import cleanup of the grouping shadow table so it doesn't grow unbounded across imports.


Before

  • No CSV import path for credit notes or credit note allocations.
  • Bulk-loading historical financial data required scripting against APIv4 directly or hand-entering everything through the UI.
  • csvimport's UI didn't list either entity because neither had an APIv3 surface.

After

Credit Note allocations

  • Pick CreditNoteAllocation in csvimport's Entity to Import dropdown.
  • Each CSV row creates one allocation plus the same accounting entries (financial transaction, entity-financial-trxn link, payment instrument, status) that APIv4's CreditNoteAllocation::allocate action produces.
  • The type_id field accepts either the numeric option value or, via a parallel type_name column, the option-value name (e.g. invoice).
  • Pre-write validation catches:
    • Missing required fields (via spec-level api.required).
    • type_id/type_name either-or rule (the only PHP-level required check, since the two are mutually exclusive).
    • Non-positive amounts.
    • Amount exceeds the credit note's remaining credit (cumulative across rows in the same import).
    • Unknown credit note id / unknown contribution id.
    • Credit note with no contact bound (refused outright).
    • Credit note and contribution belonging to different contacts.
  • CSV date column accepts any string strtotime() understands; empty or unmapped dates default to today (no more 1970-01-01 records).

Credit Note imports (with line items)

  • Pick CreditNoteImporter in csvimport's Entity to Import dropdown.
  • Rows that share a credit_note_external_id are grouped into one credit note with that many line items.
  • The end state of N rows for a given external id is identical to what APIv4 CreditNote.save produces for the same N items in one go: same credit-note record, same line records, same financial transaction (one per credit note), same financial items, same entity-financial-trxn linkage.
  • Tax is derived automatically from the financial type's Sales Tax Account is relationship via CRM_Core_PseudoConstant::getTaxRates() — no need to repeat tax rates per row.
  • The external-id mapping table is wiped before each import starts, so the table never grows across runs.
  • Pre-write validation catches:
    • Missing required fields.
    • Unknown contact (by id or external identifier) / unknown owner organisation / unknown financial type.
    • Date strings that strtotime() can't parse → clear "Could not parse date" error.
    • Empty / unmapped date columns → defaults to today.
    • Two rows that share an external id but disagree on customer or owner organisation → the second row is rejected.
  • CSV row failures show in csvimport's per-row error column; the rest of the file continues importing.

Technical details

New entities

Both entities expose APIv3 (for csvimport) and APIv4 (so csvimport's reference-field discovery via civicrm_api4($entity, 'getFields') doesn't 404).

New / modified files

Bulk-import surface

  • api/v3/CreditNoteAllocation.php — APIv3 wrapper that delegates to CRM_Financeextras_BAO_CreditNoteAllocation::createWithAccountingEntries. Adds type_name pseudo-constant resolver, amount/contact validators sharing a single fetch of the credit note + contribution, and an epoch-aware date helper.
  • api/v3/CreditNoteImporter.php — APIv3 entry point that delegates to CRM_Financeextras_CreditNoteImporter_CSVRowImporter. Spec exposes the CSV columns to csvimport's column-mapping UI.
  • Civi/Api4/CreditNoteImporter.php — APIv4 wrapper extending Generic\DAOEntity (required so civicrm_api4('CreditNoteImporter', 'getFields', …) succeeds; without it csvimport surfaces "API (CreditNoteImporter, getFields) does not exist" on every row).

Importer internals (CreditNoteImporter)

  • CRM/Financeextras/DAO/CreditNoteImporter.php — fake DAO; fields() drives csvimport's column-mapping UI.
  • CRM/Financeextras/BAO/CreditNoteImporter.php — empty BAO for convention.
  • CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php — the row processor. First row in an external-id group calls CRM_Financeextras_BAO_CreditNote::createWithAccountingEntries (matches the APIv4 save path); subsequent rows reuse the same financial transaction, append a line via CRM_Financeextras_BAO_CreditNoteLine::createWithAcountingEntries, and re-aggregate the credit note totals from the persisted lines so per-line rounding never drifts.
  • CRM/Financeextras/CreditNoteImporter/Exception/InvalidRowException.php — typed exception for row-level failures.
  • xml/schema/CRM/Financeextras/CreditNoteImporter.entityType.php — registers the entity name with CiviCRM.

External-id mapping

  • The mapping (credit_note_external_id → internal credit note id) lives in a CiviCRM custom group attached to CreditNote, identical in pattern to membershipextrasimporterapi's civicrm_value_*_ext_id tables. CiviCRM auto-creates the shadow table civicrm_value_credit_note_ext_id.
  • xml/customGroups_install.xml — declares the credit_note_external_id custom group and field.
  • Civi/Financeextras/Setup/Manage/CreditNoteCustomGroupExtensionManager.php — registers the CreditNote entity in the cg_extend_objects option group so CiviCRM accepts the <extends>CreditNote</extends> declaration. Wired into the upgrader's postInstall, enable, disable, uninstall lists.
  • Read/write of the shadow table use raw SQL (SELECT entity_id FROM civicrm_value_credit_note_ext_id / INSERT INTO …); APIv4 custom-field reads/writes do not reliably persist for extension-defined DAO entities, and using raw SQL matches what membershipextrasimporterapi does.
  • Civi/Financeextras/Hook/BuildForm/CreditNoteImporterPreImportCleanup.php — wipes the shadow table when the user lands on csvimport's Preview form for our entity. Hooked into hook_civicrm_buildForm (not hook_civicrm_postProcess) because CRM_Import_Form_Preview::postProcess() calls runAllInteractive()CRM_Utils_System::redirect() and exits the request, so postProcessHook() never fires.
  • Registered in financeextras.php's existing financeextras_civicrm_buildForm hook dispatcher.

Upgrader

  • CRM/Financeextras/Upgrader.php — added upgrade_1007() that drops legacy artefacts from earlier iterations of this work (a standalone mapping table, a direct external_id column on the credit-note table) and then installs the cg_extend_objects entry + custom group via the shared manager. postInstall also installs the custom group. The new manager is added to all four lifecycle methods (postInstall, enable, disable, uninstall).

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request adds bulk-import functionality for Credit Notes and Credit Note Allocations to the CiviCRM financeextras extension. By exposing these entities through new APIv3 wrappers, users can now leverage the csvimport extension to load financial data that previously required manual entry or custom scripting. The implementation ensures that accounting entries are generated consistently with existing APIv4 logic, includes robust validation for data consistency, and handles grouping of line items based on external identifiers.

Highlights

  • New APIv3 Entities: Introduced CreditNoteAllocation and CreditNoteImporter entities to enable CSV import support via the csvimport extension.
  • Credit Note Importer Logic: Implemented row-based importing where rows sharing an external ID are grouped into a single credit note, supporting automatic tax derivation and accounting entry generation.
  • Validation and Cleanup: Added comprehensive pre-write validation for data integrity and a pre-import cleanup hook to manage the external-id mapping table.
  • Database Schema: Added a custom group and shadow table to track external ID mappings for credit notes.
Ignored Files
  • Ignored by pattern: CRM/*/DAO/*.php (1)
    • CRM/Financeextras/DAO/CreditNoteImporter.php
  • Ignored by pattern: xml/schema/*.entityType.php (1)
    • xml/schema/CRM/Financeextras/CreditNoteImporter.entityType.php
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a Credit Note Importer system, featuring a CSV row importer, APIv3/v4 entities, and allocation management. The implementation handles grouping CSV rows by external ID and manages the associated accounting entries. Review feedback identifies a critical race condition in the pre-import cleanup hook that could lead to data corruption in multi-user environments. Additionally, the feedback highlights several style guide violations regarding internationalization and mandatory guards for APIv4 results, as well as logic issues in date parsing for historical records.

Comment thread Civi/Financeextras/Hook/BuildForm/CreditNoteImporterPreImportCleanup.php Outdated
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php Outdated
Comment thread api/v3/CreditNoteAllocation.php Outdated
Comment thread api/v3/CreditNoteAllocation.php
@shahrukh-compuco shahrukh-compuco force-pushed the fosfa-473-add-credit-note-import-functionality branch 2 times, most recently from c9dfade to 5280b6a Compare May 4, 2026 09:13
@shahrukh-compuco
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a robust Credit Note importer system, featuring a CSV row importer, APIv3 endpoints for importing and allocation, and database migrations to support external ID tracking. The feedback highlights several critical and medium-severity issues, primarily focusing on missing is_array() guards for APIv4 ->first() results as required by the repository style guide. Addressing these is necessary to prevent fatal errors when records are missing. Additionally, a validation check for positive line unit prices should be added to the importer to maintain accounting integrity.

Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread CRM/Financeextras/CreditNoteImporter/CSVRowImporter.php
Comment thread api/v3/CreditNoteAllocation.php
Comment thread api/v3/CreditNoteAllocation.php
@shahrukh-compuco shahrukh-compuco force-pushed the fosfa-473-add-credit-note-import-functionality branch from 5280b6a to 1ac1d33 Compare May 4, 2026 09:25
@shahrukh-compuco shahrukh-compuco force-pushed the fosfa-473-add-credit-note-import-functionality branch 2 times, most recently from efc37e2 to e6a3d92 Compare May 4, 2026 10:21
@shahrukh-compuco shahrukh-compuco force-pushed the fosfa-473-add-credit-note-import-functionality branch from e6a3d92 to f61c566 Compare May 5, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants