Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions CRM/Financeextras/DAO/Company.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class CRM_Financeextras_DAO_Company extends CRM_Core_DAO {
*/
public $receivable_payment_method;

/**
* Financial type to use for overpayment credit notes
*
* @var int|string|null
* (SQL type: int unsigned)
* Note that values will be retrieved from the database as a string.
*/
public $overpayment_financial_type_id;

/**
* Class constructor.
*/
Expand Down Expand Up @@ -132,6 +141,7 @@ public static function getReferenceColumns() {
Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id');
Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'invoice_template_id', 'civicrm_msg_template', 'id');
Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'creditnote_template_id', 'civicrm_msg_template', 'id');
Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'overpayment_financial_type_id', 'civicrm_financial_type', 'id');
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
}
return Civi::$statics[__CLASS__]['links'];
Expand Down Expand Up @@ -293,6 +303,27 @@ public static function &fields() {
],
'add' => NULL,
],
'overpayment_financial_type_id' => [
'name' => 'overpayment_financial_type_id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Financial Type for Overpayments'),
'description' => E::ts('Financial type to use for overpayment credit notes'),
'where' => 'financeextras_company.overpayment_financial_type_id',
'table_name' => 'financeextras_company',
'entity' => 'Company',
'bao' => 'CRM_Financeextras_DAO_Company',
'localizable' => 0,
'FKClassName' => 'CRM_Financial_DAO_FinancialType',
'html' => [
'label' => E::ts("Financial Type for Overpayments"),
],
'pseudoconstant' => [
'table' => 'civicrm_financial_type',
'keyColumn' => 'id',
'labelColumn' => 'name',
],
'add' => NULL,
],
];
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
}
Expand Down
15 changes: 15 additions & 0 deletions CRM/Financeextras/Form/Company/Add.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ public function buildQuickForm() {
TRUE
);

$this->addEntityRef(
'overpayment_financial_type_id',
ts('Financial Type for Overpayments'),
[
'entity' => 'FinancialType',
'api' => ['params' => ['is_active' => 1]],
'select' => ['minimumInputLength' => 0],
'placeholder' => ts('Select Financial Type'),
'multiple' => FALSE,
],
FALSE
);

$this->addButtons([
[
'type' => 'submit',
Expand Down Expand Up @@ -111,6 +124,7 @@ public function setDefaultValues() {
$values['creditnote_prefix'] = $company->creditnote_prefix;
$values['next_creditnote_number'] = $company->next_creditnote_number;
$values['receivable_payment_method'] = $company->receivable_payment_method;
$values['overpayment_financial_type_id'] = $company->overpayment_financial_type_id;

return $values;
}
Expand Down Expand Up @@ -159,6 +173,7 @@ public function postProcess() {
$params['creditnote_prefix'] = $submittedValues['creditnote_prefix'];
$params['next_creditnote_number'] = $submittedValues['next_creditnote_number'];
$params['receivable_payment_method'] = $submittedValues['receivable_payment_method'];
$params['overpayment_financial_type_id'] = !empty($submittedValues['overpayment_financial_type_id']) ? $submittedValues['overpayment_financial_type_id'] : NULL;

CRM_Financeextras_BAO_Company::create($params);

Expand Down
105 changes: 105 additions & 0 deletions CRM/Financeextras/Form/Contribute/OverpaymentAllocate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

use Civi\Api4\CreditNote;
use Civi\Api4\Contribution;
use Civi\Financeextras\Utils\OverpaymentUtils;
use CRM_Financeextras_ExtensionUtil as E;

/**
* Form to allocate overpayment to a new credit note.
*/
class CRM_Financeextras_Form_Contribute_OverpaymentAllocate extends CRM_Core_Form {

/**
* Contribution ID.
*
* @var int
*/
protected $contributionId;
/**
* Contribution data.
*
* @var array
*/
protected $contribution;
/**
* Overpayment amount.
*
* @var float
*/
protected $overpaymentAmount;

/**
* {@inheritDoc}
*/
public function preProcess() {
$this->contributionId = CRM_Utils_Request::retrieve('contribution_id', 'Positive', $this, TRUE);

// Validate eligibility.
if (!OverpaymentUtils::isEligibleForOverpaymentAllocation($this->contributionId)) {
throw new CRM_Core_Exception(E::ts('This contribution is not eligible for overpayment allocation.'));
}

$this->contribution = Contribution::get(FALSE)
->addWhere('id', '=', $this->contributionId)
->addSelect('*', 'contact_id.display_name')
->execute()
->first();

$this->overpaymentAmount = OverpaymentUtils::getOverpaymentAmount($this->contributionId);

CRM_Utils_System::setTitle(E::ts('Allocate overpayment to credit note'));
}

/**
* {@inheritDoc}
*/
public function buildQuickForm() {
$currencySymbol = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_Currency', $this->contribution['currency'], 'symbol', 'name');
$formattedAmount = CRM_Utils_Money::format($this->overpaymentAmount, $this->contribution['currency']);

$this->assign('message', E::ts('Allocate an overpaid amount to a new credit note. The credit can then be applied to future invoices. This cannot be undone.'));
$this->assign('overpaymentAmount', $formattedAmount);
$this->assign('contactName', $this->contribution['contact_id.display_name']);

$this->addButtons([
[
'type' => 'submit',
'name' => E::ts('Save'),
'isDefault' => TRUE,
],
[
'type' => 'cancel',
'name' => E::ts('Cancel'),
],
]);

parent::buildQuickForm();
}

/**
* {@inheritDoc}
*/
public function postProcess() {
try {
$result = CreditNote::allocateOverpayment(FALSE)
->setContributionId($this->contributionId)
->execute()
->first();

CRM_Core_Session::setStatus(
E::ts('Overpayment allocated to credit note "%1" successfully.', [1 => $result['cn_number']]),
E::ts('Success'),
'success'
);
}
catch (\Throwable $th) {
CRM_Core_Session::setStatus(
E::ts('Error allocating overpayment: %1', [1 => $th->getMessage()]),
E::ts('Error'),
'error'
);
}
}

}
6 changes: 4 additions & 2 deletions CRM/Financeextras/Page/Company.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ public function run() {
public function browse() {
$optionGroupId = civicrm_api3('OptionGroup', 'Getvalue', ['name' => 'payment_instrument', 'return' => 'id']);

$getQuery = 'SELECT fc.*, cc.display_name as company_name, mt.msg_title as invoice_template_name, mt2.msg_title as creditnote_template_name, ov.label AS receivable_payment_method FROM financeextras_company fc
$getQuery = 'SELECT fc.*, cc.display_name as company_name, mt.msg_title as invoice_template_name, mt2.msg_title as creditnote_template_name, ov.label AS receivable_payment_method, ft.name AS overpayment_financial_type_name FROM financeextras_company fc
LEFT JOIN civicrm_contact cc on cc.id = fc.contact_id
LEFT JOIN civicrm_msg_template mt ON mt.id = fc.invoice_template_id
LEFT JOIN civicrm_msg_template mt2 ON mt2.id = fc.creditnote_template_id
LEFT JOIN civicrm_option_value ov ON ov.value = fc.receivable_payment_method AND ov.option_group_id = ' . $optionGroupId . ' ORDER BY fc.id DESC
LEFT JOIN civicrm_option_value ov ON ov.value = fc.receivable_payment_method AND ov.option_group_id = ' . $optionGroupId . '
LEFT JOIN civicrm_financial_type ft ON ft.id = fc.overpayment_financial_type_id
ORDER BY fc.id DESC
Comment on lines +14 to +20
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

[nitpick] SQL query is difficult to read and maintain as a single concatenated string. Consider using a multi-line heredoc or nowdoc syntax for better readability and maintainability.

Copilot uses AI. Check for mistakes.
';
$company = CRM_Core_DAO::executeQuery($getQuery);
$rows = [];
Expand Down
13 changes: 13 additions & 0 deletions CRM/Financeextras/Upgrader.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,17 @@ public function upgrade_1005(): bool {
}
}

/**
* Add overpayment_financial_type_id to Company table.
*/
public function upgrade_1006(): bool {
$this->ctx->log->info('Applying update 1006 - Adding overpayment_financial_type_id to Company');

if (!\CRM_Core_BAO_SchemaHandler::checkIfFieldExists('financeextras_company', 'overpayment_financial_type_id', FALSE)) {
$this->executeSqlFile('sql/upgrade_1006.sql');
}

return TRUE;
}

}
Loading