/** * Global form rule. * * @param array $fields * The input form values. * @param array $files * The uploaded files if any. * @param $self * * @return bool|array * true if no errors, else array of errors */ public static function formRule($fields, $files, $self) { $errors = array(); // Check for Credit Card Contribution. if ($self->_mode) { if (empty($fields['payment_processor_id'])) { $errors['payment_processor_id'] = ts('Payment Processor is a required field.'); } else { // validate payment instrument (e.g. credit card number) CRM_Core_Payment_Form::validatePaymentInstrument($fields['payment_processor_id'], $fields, $errors, NULL); } } // Do the amount validations. if (empty($fields['total_amount']) && empty($self->_lineItems)) { if ($priceSetId = CRM_Utils_Array::value('price_set_id', $fields)) { CRM_Price_BAO_PriceField::priceSetValidation($priceSetId, $fields, $errors); } } $softErrors = CRM_Contribute_Form_SoftCredit::formRule($fields, $errors, $self); if (!empty($fields['total_amount']) && (!empty($fields['net_amount']) || !empty($fields['fee_amount']))) { $sum = CRM_Utils_Rule::cleanMoney($fields['net_amount']) + CRM_Utils_Rule::cleanMoney($fields['fee_amount']); // For taxable contribution we need to deduct taxable amount from // (net amount + fee amount) before comparing it with total amount if (!empty($self->_values['tax_amount'])) { $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($self->_id); if (!(CRM_Utils_Array::value('membership', $componentDetails) || CRM_Utils_Array::value('participant', $componentDetails))) { $sum = CRM_Utils_Money::format($sum - $self->_values['tax_amount'], NULL, '%a'); } } if (CRM_Utils_Rule::cleanMoney($fields['total_amount']) != $sum) { $errors['total_amount'] = ts('The sum of fee amount and net amount must be equal to total amount'); } } //CRM-16285 - Function to handle validation errors on form, for recurring contribution field. CRM_Contribute_BAO_ContributionRecur::validateRecurContribution($fields, $files, $self, $errors); // Form rule for status http://wiki.civicrm.org/confluence/display/CRM/CiviAccounts+4.3+Data+Flow if ($self->_action & CRM_Core_Action::UPDATE && $self->_id && $self->_values['contribution_status_id'] != $fields['contribution_status_id']) { CRM_Contribute_BAO_Contribution::checkStatusValidation($self->_values, $fields, $errors); } // CRM-16015, add form-rule to restrict change of financial type if using price field of different financial type if ($self->_action & CRM_Core_Action::UPDATE && $self->_id && $self->_values['financial_type_id'] != $fields['financial_type_id']) { CRM_Contribute_BAO_Contribution::checkFinancialTypeChange(NULL, $self->_id, $errors); } //FIXME FOR NEW DATA FLOW http://wiki.civicrm.org/confluence/display/CRM/CiviAccounts+4.3+Data+Flow if (!empty($fields['fee_amount']) && !empty($fields['financial_type_id']) && ($financialType = CRM_Contribute_BAO_Contribution::validateFinancialType($fields['financial_type_id']))) { $errors['financial_type_id'] = ts("Financial Account of account relationship of 'Expense Account is' is not configured for Financial Type : ") . $financialType; } // $trxn_id must be unique CRM-13919 if (!empty($fields['trxn_id'])) { $queryParams = array(1 => array($fields['trxn_id'], 'String')); $query = 'select count(*) from civicrm_contribution where trxn_id = %1'; if ($self->_id) { $queryParams[2] = array((int) $self->_id, 'Integer'); $query .= ' and id !=%2'; } $tCnt = CRM_Core_DAO::singleValueQuery($query, $queryParams); if ($tCnt) { $errors['trxn_id'] = ts('Transaction ID\'s must be unique. Transaction \'%1\' already exists in your database.', array(1 => $fields['trxn_id'])); } } if (!empty($fields['revenue_recognition_date']) && count(array_filter($fields['revenue_recognition_date'])) == 1) { $errors['revenue_recognition_date'] = ts('Month and Year are required field for Revenue Recognition.'); } // CRM-16189 try { CRM_Financial_BAO_FinancialAccount::checkFinancialTypeHasDeferred($fields, $self->_id, $self->_priceSet['fields']); } catch (CRM_Core_Exception $e) { $errors['financial_type_id'] = ' '; $errors['_qf_default'] = $e->getMessage(); } $errors = array_merge($errors, $softErrors); return $errors; }
/** * Test for validating financial type has deferred revenue account relationship. */ public function testcheckFinancialTypeHasDeferred() { Civi::settings()->set('contribution_invoice_settings', array('deferred_revenue_enabled' => '1')); $params = array(); $valid = CRM_Financial_BAO_FinancialAccount::checkFinancialTypeHasDeferred($params); $this->assertFalse($valid, "This should have been false"); $cid = $this->individualCreate(); $params = array('contact_id' => $cid, 'receive_date' => '2016-01-20', 'total_amount' => 100, 'financial_type_id' => 4, 'revenue_recognition_date' => date('Ymd', strtotime("+1 month")), 'line_items' => array(array('line_item' => array(array('entity_table' => 'civicrm_contribution', 'price_field_id' => 8, 'price_field_value_id' => 16, 'label' => 'test 1', 'qty' => 1, 'unit_price' => 100, 'line_total' => 100, 'financial_type_id' => 4), array('entity_table' => 'civicrm_contribution', 'price_field_id' => 8, 'price_field_value_id' => 17, 'label' => 'Test 2', 'qty' => 1, 'unit_price' => 200, 'line_total' => 200, 'financial_type_id' => 4))))); try { CRM_Financial_BAO_FinancialAccount::checkFinancialTypeHasDeferred($params); } catch (CRM_Core_Exception $e) { $this->fail("Missed expected exception"); } $params = array('contact_id' => $cid, 'receive_date' => '2016-01-20', 'total_amount' => 100, 'financial_type_id' => 1, 'revenue_recognition_date' => date('Ymd', strtotime("+1 month"))); try { CRM_Financial_BAO_FinancialAccount::checkFinancialTypeHasDeferred($params); $this->fail("Missed expected exception"); } catch (CRM_Core_Exception $e) { $this->assertEquals('Revenue recognition date can only be specified if the financial type selected has a deferred revenue account configured. Please have an administrator set up the deferred revenue account at Administer > CiviContribute > Financial Accounts, then configure it for financial types at Administer > CiviContribution > Financial Types, Accounts', $e->getMessage()); } }
/** * Takes an associative array and creates a contribution object. * * the function extract all the params it needs to initialize the create a * contribution object. the params array could contain additional unused name/value * pairs * * @param array $params * (reference ) an assoc array of name/value pairs. * @param array $ids * The array that holds all the db ids. * * @return CRM_Contribute_BAO_Contribution|void */ public static function add(&$params, $ids = array()) { if (empty($params)) { return NULL; } //per http://wiki.civicrm.org/confluence/display/CRM/Database+layer we are moving away from $ids array $contributionID = CRM_Utils_Array::value('contribution', $ids, CRM_Utils_Array::value('id', $params)); $duplicates = array(); if (self::checkDuplicate($params, $duplicates, $contributionID)) { $error = CRM_Core_Error::singleton(); $d = implode(', ', $duplicates); $error->push(CRM_Core_Error::DUPLICATE_CONTRIBUTION, 'Fatal', array($d), "Duplicate error - existing contribution record(s) have a matching Transaction ID or Invoice ID. Contribution record ID(s) are: {$d}"); return $error; } // first clean up all the money fields $moneyFields = array('total_amount', 'net_amount', 'fee_amount', 'non_deductible_amount'); //if priceset is used, no need to cleanup money if (!empty($params['skipCleanMoney'])) { unset($moneyFields[0]); } foreach ($moneyFields as $field) { if (isset($params[$field])) { $params[$field] = CRM_Utils_Rule::cleanMoney($params[$field]); } } //set defaults in create mode if (!$contributionID) { CRM_Core_DAO::setCreateDefaults($params, self::getDefaults()); } $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name'); //if contribution is created with cancelled or refunded status, add credit note id if (!empty($params['contribution_status_id'])) { // @todo - should we include Chargeback? If so use self::isContributionStatusNegative($params['contribution_status_id']) if ($params['contribution_status_id'] == array_search('Refunded', $contributionStatus) || $params['contribution_status_id'] == array_search('Cancelled', $contributionStatus)) { if (empty($params['creditnote_id']) || $params['creditnote_id'] == "null") { $params['creditnote_id'] = self::createCreditNoteId(); } } } else { // Since the fee amount is expecting this (later on) ensure it is always set. // It would only not be set for an update where it is unchanged. $params['contribution_status_id'] = civicrm_api3('Contribution', 'getvalue', array('id' => $contributionID, 'return' => 'contribution_status_id')); } if (!$contributionID && CRM_Utils_Array::value('membership_id', $params) && self::checkContributeSettings('deferred_revenue_enabled')) { $memberStartDate = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_Membership', $params['membership_id'], 'start_date'); if ($memberStartDate) { $params['revenue_recognition_date'] = date('Ymd', strtotime($memberStartDate)); } } self::calculateMissingAmountParams($params, $contributionID); if (!empty($params['payment_instrument_id'])) { $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument('name'); if ($params['payment_instrument_id'] != array_search('Check', $paymentInstruments)) { $params['check_number'] = 'null'; } } $setPrevContribution = TRUE; // CRM-13964 partial payment if (!empty($params['partial_payment_total']) && !empty($params['partial_amount_pay'])) { $partialAmtTotal = $params['partial_payment_total']; $partialAmtPay = $params['partial_amount_pay']; $params['total_amount'] = $partialAmtTotal; if ($partialAmtPay < $partialAmtTotal) { $params['contribution_status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Partially paid', 'name'); $params['is_pay_later'] = 0; $setPrevContribution = FALSE; } } if ($contributionID && $setPrevContribution) { $params['prevContribution'] = self::getOriginalContribution($contributionID); } // CRM-16189 CRM_Financial_BAO_FinancialAccount::checkFinancialTypeHasDeferred($params, $contributionID); if ($contributionID && !empty($params['revenue_recognition_date']) && !empty($params['prevContribution']) && !($contributionStatus[$params['prevContribution']->contribution_status_id] == 'Pending') && !self::allowUpdateRevenueRecognitionDate($contributionID)) { unset($params['revenue_recognition_date']); } if (!isset($params['tax_amount']) && $setPrevContribution && (isset($params['total_amount']) || isset($params['financial_type_id']))) { $params = CRM_Contribute_BAO_Contribution::checkTaxAmount($params); } if ($contributionID) { CRM_Utils_Hook::pre('edit', 'Contribution', $contributionID, $params); } else { CRM_Utils_Hook::pre('create', 'Contribution', NULL, $params); } $contribution = new CRM_Contribute_BAO_Contribution(); $contribution->copyValues($params); $contribution->id = $contributionID; if (empty($contribution->id)) { // (only) on 'create', make sure that a valid currency is set (CRM-16845) if (!CRM_Utils_Rule::currencyCode($contribution->currency)) { $contribution->currency = CRM_Core_Config::singleton()->defaultCurrency; } } $result = $contribution->save(); // Add financial_trxn details as part of fix for CRM-4724 $contribution->trxn_result_code = CRM_Utils_Array::value('trxn_result_code', $params); $contribution->payment_processor = CRM_Utils_Array::value('payment_processor', $params); //add Account details $params['contribution'] = $contribution; self::recordFinancialAccounts($params); if (self::isUpdateToRecurringContribution($params)) { CRM_Contribute_BAO_ContributionRecur::updateOnNewPayment(!empty($params['contribution_recur_id']) ? $params['contribution_recur_id'] : $params['prevContribution']->contribution_recur_id, $contributionStatus[$params['contribution_status_id']]); } CRM_Contact_BAO_GroupContactCache::opportunisticCacheFlush(); if ($contributionID) { CRM_Utils_Hook::post('edit', 'Contribution', $contribution->id, $contribution); } else { CRM_Utils_Hook::post('create', 'Contribution', $contribution->id, $contribution); } return $result; }