From: Kurund Jalmi Date: Mon, 23 Nov 2015 17:31:42 +0000 (+0530) Subject: Merge remote-tracking branch 'upstream/4.6' into 4.6-master-2015-11-23-22-46-27 X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=a5b08a92f51c39c668edf05a5db3b199a300ddde;p=civicrm-core.git Merge remote-tracking branch 'upstream/4.6' into 4.6-master-2015-11-23-22-46-27 --- a5b08a92f51c39c668edf05a5db3b199a300ddde diff --cc CRM/Contribute/BAO/Contribution.php index ac07672092,c4f704525c..8af434153b --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@@ -3404,10 -3206,11 +3405,12 @@@ INNER JOIN civicrm_activity ON civicrm_ 'Pending' => array('Cancelled', 'Completed', 'Failed'), 'In Progress' => array('Cancelled', 'Completed', 'Failed'), 'Refunded' => array('Cancelled', 'Completed'), + 'Partially paid' => array('Completed'), ); - if (!in_array($contributionStatuses[$fields['contribution_status_id']], $checkStatus[$contributionStatuses[$values['contribution_status_id']]])) { + if (!in_array($contributionStatuses[$fields['contribution_status_id']], + CRM_Utils_Array::value($contributionStatuses[$values['contribution_status_id']], $checkStatus, array())) + ) { $errors['contribution_status_id'] = ts("Cannot change contribution status from %1 to %2.", array( 1 => $contributionStatuses[$values['contribution_status_id']], 2 => $contributionStatuses[$fields['contribution_status_id']], diff --cc CRM/Contribute/Form/Contribution.php index 71de60eb75,000f247fa2..4467a93d76 --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@@ -1318,522 -1913,4 +1318,522 @@@ class CRM_Contribute_Form_Contribution } } + /** + * Wrapper for unit testing the post process submit function. + * + * (If we expose through api we can get default additions 'for free'). + * + * @param array $params + * @param int $action + * @param string|null $creditCardMode + * + * @throws \CiviCRM_API3_Exception + */ + public function testSubmit($params, $action, $creditCardMode = NULL) { + $defaults = array( + 'soft_credit_contact_id' => array(), + 'receipt_date' => '', + 'receipt_date_time' => '', + 'cancel_date' => '', + 'cancel_date_time' => '', + 'hidden_Premium' => 1, + ); + $this->_bltID = 5; + if (!empty($params['id'])) { + $existingContribution = civicrm_api3('contribution', 'getsingle', array( + 'id' => $params['id'], + )); + $this->_id = $params['id']; + } + else { + $existingContribution = array(); + } + + $this->_defaults['contribution_status_id'] = CRM_Utils_Array::value('contribution_status_id', + $existingContribution + ); + + $this->_defaults['total_amount'] = CRM_Utils_Array::value('total_amount', + $existingContribution + ); + + if ($creditCardMode) { + $this->_mode = $creditCardMode; + } + + // Required because processCreditCard calls set method on this. + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->controller = new CRM_Core_Controller(); + + CRM_Contribute_Form_AdditionalInfo::buildPremium($this); + + $this->_fields = array(); + $this->submit(array_merge($defaults, $params), $action, CRM_Utils_Array::value('pledge_payment_id', $params)); + + } + + /** + * @param array $submittedValues + * + * @param int $action + * Action constant + * - CRM_Core_Action::UPDATE + * + * @param $pledgePaymentID + * + * @return array + * @throws \Exception + */ + protected function submit($submittedValues, $action, $pledgePaymentID) { + $softParams = $softIDs = array(); + $pId = $contribution = $isRelatedId = FALSE; + $this->_params = $submittedValues; + $this->beginPostProcess(); + + if (!empty($submittedValues['price_set_id']) && $action & CRM_Core_Action::UPDATE) { + $line = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'contribution'); + $lineID = key($line); + $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', CRM_Utils_Array::value('price_field_id', $line[$lineID]), 'price_set_id'); + $quickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config'); + // Why do we do this? Seems like a like a wrapper for old functionality - but single line price sets & quick + // config should be treated the same. + if ($quickConfig) { + CRM_Price_BAO_LineItem::deleteLineItems($this->_id, 'civicrm_contribution'); + } + } + + // Process price set and get total amount and line items. + $lineItem = array(); + $priceSetId = CRM_Utils_Array::value('price_set_id', $submittedValues); + if (empty($priceSetId) && !$this->_id) { + $this->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name'); + $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId)); + $fieldID = key($this->_priceSet['fields']); + $fieldValueId = key($this->_priceSet['fields'][$fieldID]['options']); + $this->_priceSet['fields'][$fieldID]['options'][$fieldValueId]['amount'] = $submittedValues['total_amount']; + $submittedValues['price_' . $fieldID] = 1; + } + + // Every contribution has a price-set - the only reason it shouldn't be set is if we are dealing with + // quick config (very very arguably) & yet we see that this could still be quick config so this should be understood + // as a point of fragility rather than a logical 'if' clause. + if ($priceSetId) { + CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], + $submittedValues, $lineItem[$priceSetId]); + // Unset tax amount for offline 'is_quick_config' contribution. + // @todo WHY - quick config was conceived as a quick way to configure contribution forms. + // this is an example of 'other' functionality being hung off it. + if ($this->_priceSet['is_quick_config'] && + !array_key_exists($submittedValues['financial_type_id'], CRM_Core_PseudoConstant::getTaxRates()) + ) { + unset($submittedValues['tax_amount']); + } + $submittedValues['total_amount'] = CRM_Utils_Array::value('amount', $submittedValues); + } + if ($this->_id) { + if ($this->_compId) { + if ($this->_context == 'participant') { + $pId = $this->_compId; + } + elseif ($this->_context == 'membership') { + $isRelatedId = TRUE; + } + else { + $pId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_id, 'participant_id', 'contribution_id'); + } + } + else { + $contributionDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); + if (array_key_exists('membership', $contributionDetails)) { + $isRelatedId = TRUE; + } + elseif (array_key_exists('participant', $contributionDetails)) { + $pId = $contributionDetails['participant']; + } + } + } + + if (!$priceSetId && !empty($submittedValues['total_amount']) && $this->_id) { + // CRM-10117 update the line items for participants. + // @todo - if we are completing a contribution then the api call + // civicrm_api3('Contribution', 'completetransaction') should take care of + // all associated updates rather than replicating them on the form layer. + if ($pId) { + $entityTable = 'participant'; + $entityID = $pId; + $isRelatedId = FALSE; + $participantParams = array( + 'fee_amount' => $submittedValues['total_amount'], + 'id' => $entityID, + ); + CRM_Event_BAO_Participant::add($participantParams); + if (empty($this->_lineItems)) { + $this->_lineItems[] = CRM_Price_BAO_LineItem::getLineItems($entityID, 'participant', 1); + } + } + else { + $entityTable = 'contribution'; + $entityID = $this->_id; + } + + $lineItems = CRM_Price_BAO_LineItem::getLineItems($entityID, $entityTable, NULL, TRUE, $isRelatedId); + foreach (array_keys($lineItems) as $id) { + $lineItems[$id]['id'] = $id; + } + $itemId = key($lineItems); + if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) { + $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id'); + } + + // @todo see above - new functionality has been inappropriately added to the quick config concept + // and new functionality has been added onto the form layer rather than the BAO :-( + if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { + //CRM-16833: Ensure tax is applied only once for membership conribution, when status changed.(e.g Pending to Completed). + $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); - if (!CRM_Utils_Array::value('membership', $componentDetails) || !CRM_Utils_Array::value('participant', $componentDetails)) { ++ if (!(CRM_Utils_Array::value('membership', $componentDetails) || CRM_Utils_Array::value('participant', $componentDetails))) { + if (!($this->_action & CRM_Core_Action::UPDATE && (($this->_defaults['contribution_status_id'] != $submittedValues['contribution_status_id'])))) { + $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues)); + } + } + + // Update line total and total amount with tax on edit. + $financialItemsId = CRM_Core_PseudoConstant::getTaxRates(); + if (array_key_exists($submittedValues['financial_type_id'], $financialItemsId)) { + $lineItems[$itemId]['tax_rate'] = $financialItemsId[$submittedValues['financial_type_id']]; + } + else { + $lineItems[$itemId]['tax_rate'] = $lineItems[$itemId]['tax_amount'] = ""; + $submittedValues['tax_amount'] = 'null'; + } + if ($lineItems[$itemId]['tax_rate']) { + $lineItems[$itemId]['tax_amount'] = ($lineItems[$itemId]['tax_rate'] / 100) * $lineItems[$itemId]['line_total']; + $submittedValues['total_amount'] = $lineItems[$itemId]['line_total'] + $lineItems[$itemId]['tax_amount']; + $submittedValues['tax_amount'] = $lineItems[$itemId]['tax_amount']; + } + } + // CRM-10117 update the line items for participants. + if (!empty($lineItems[$itemId]['price_field_id'])) { + $lineItem[$this->_priceSetId] = $lineItems; + } + } + + $isQuickConfig = 0; + if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { + $isQuickConfig = 1; + } + //CRM-11529 for quick config back office transactions + //when financial_type_id is passed in form, update the + //line items with the financial type selected in form + // NOTE that this IS still a legitimate use of 'quick-config' for contributions under the current DB but + // we should look at having a price field per contribution type & then there would be little reason + // for the back-office contribution form postProcess to know if it is a quick-config form. + if ($isQuickConfig && !empty($submittedValues['financial_type_id']) && CRM_Utils_Array::value($this->_priceSetId, $lineItem) + ) { + foreach ($lineItem[$this->_priceSetId] as &$values) { + $values['financial_type_id'] = $submittedValues['financial_type_id']; + } + } + + if (!isset($submittedValues['total_amount'])) { + $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values); + } + $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE); + + $isEmpty = array_keys(array_flip($submittedValues['soft_credit_contact_id'])); + if ($this->_id && count($isEmpty) == 1 && key($isEmpty) == NULL) { + //Delete existing soft credit records if soft credit list is empty on update + CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id, 'pcp_id' => 0)); + } + + // set the contact, when contact is selected + if (!empty($submittedValues['contact_id'])) { + $this->_contactID = $submittedValues['contact_id']; + } + + $formValues = $submittedValues; + + // Credit Card Contribution. + if ($this->_mode) { + $paramsSetByPaymentProcessingSubsystem = array( + 'trxn_id', + 'payment_instrument_id', + 'contribution_status_id', + 'cancel_date', + 'cancel_reason', + ); + foreach ($paramsSetByPaymentProcessingSubsystem as $key) { + if (isset($formValues[$key])) { + unset($formValues[$key]); + } + } + $contribution = $this->processCreditCard($formValues, $lineItem, $this->_contactID); + foreach ($paramsSetByPaymentProcessingSubsystem as $key) { + $formValues[$key] = $contribution->$key; + } + } + else { + // Offline Contribution. + $submittedValues = $this->unsetCreditCardFields($submittedValues); + + // get the required field value only. + + $params = $ids = array(); + + $params['contact_id'] = $this->_contactID; + $params['currency'] = $this->getCurrency($submittedValues); + + //format soft-credit/pcp param first + CRM_Contribute_BAO_ContributionSoft::formatSoftCreditParams($submittedValues, $this); + $params = array_merge($params, $submittedValues); + + $fields = array( + 'financial_type_id', + 'contribution_status_id', + 'payment_instrument_id', + 'cancel_reason', + 'source', + 'check_number', + ); + foreach ($fields as $f) { + $params[$f] = CRM_Utils_Array::value($f, $formValues); + } + + // CRM-5740 if priceset is used, no need to cleanup money. + if ($priceSetId) { + $params['skipCleanMoney'] = 1; + } + + $dates = array( + 'receive_date', + 'receipt_date', + 'cancel_date', + ); + + foreach ($dates as $d) { + $params[$d] = CRM_Utils_Date::processDate($formValues[$d], $formValues[$d . '_time'], TRUE); + } + + if (!empty($formValues['is_email_receipt'])) { + $params['receipt_date'] = date("Y-m-d"); + } + + if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Cancelled', 'name') + || $params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name') + ) { + if (CRM_Utils_System::isNull(CRM_Utils_Array::value('cancel_date', $params))) { + $params['cancel_date'] = date('Y-m-d'); + } + } + else { + $params['cancel_date'] = $params['cancel_reason'] = 'null'; + } + + // Set is_pay_later flag for back-office offline Pending status contributions CRM-8996 + // else if contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041 + if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name')) { + $params['is_pay_later'] = 1; + } + elseif ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name')) { + $params['is_pay_later'] = 0; + } + + $ids['contribution'] = $params['id'] = $this->_id; + + // Add Additional common information to formatted params. + CRM_Contribute_Form_AdditionalInfo::postProcessCommon($formValues, $params, $this); + if ($pId) { + $params['contribution_mode'] = 'participant'; + $params['participant_id'] = $pId; + $params['skipLineItem'] = 1; + } + elseif ($isRelatedId) { + $params['contribution_mode'] = 'membership'; + } + $params['line_item'] = $lineItem; + $params['payment_processor_id'] = $params['payment_processor'] = CRM_Utils_Array::value('id', $this->_paymentProcessor); + if (isset($submittedValues['tax_amount'])) { + $params['tax_amount'] = $submittedValues['tax_amount']; + } + //create contribution. + if ($isQuickConfig) { + $params['is_quick_config'] = 1; + } + $params['non_deductible_amount'] = $this->calculateNonDeductibleAmount($params, $formValues); + + // we are already handling note below, so to avoid duplicate notes against $contribution + if (!empty($params['note']) && !empty($submittedValues['note'])) { + unset($params['note']); + } + $contribution = CRM_Contribute_BAO_Contribution::create($params, $ids); + + // process associated membership / participant, CRM-4395 + if ($contribution->id && $action & CRM_Core_Action::UPDATE) { + $this->statusMessage[] = $this->updateRelatedComponent($contribution->id, + $contribution->contribution_status_id, + CRM_Utils_Array::value('contribution_status_id', + $this->_values + ), + $contribution->receive_date + ); + } + + array_unshift($this->statusMessage, ts('The contribution record has been saved.')); + + $this->invoicingPostProcessHook($submittedValues, $action, $lineItem); + + //send receipt mail. + if ($contribution->id && !empty($formValues['is_email_receipt'])) { + $formValues['contact_id'] = $this->_contactID; + $formValues['contribution_id'] = $contribution->id; + + $formValues += CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id); + + // to get 'from email id' for send receipt + $this->fromEmailId = $formValues['from_email_address']; + if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $formValues)) { + $this->statusMessage[] = ts('A receipt has been emailed to the contributor.'); + } + } + + $this->statusMessageTitle = ts('Saved'); + + } + + if ($contribution->id && isset($formValues['product_name'][0])) { + CRM_Contribute_Form_AdditionalInfo::processPremium($submittedValues, $contribution->id, + $this->_premiumID, $this->_options + ); + } + + if ($contribution->id && !empty($submittedValues['note'])) { + CRM_Contribute_Form_AdditionalInfo::processNote($submittedValues, $this->_contactID, $contribution->id, $this->_noteID); + } + + CRM_Core_Session::setStatus(implode(' ', $this->statusMessage), $this->statusMessageTitle, 'success'); + + CRM_Contribute_BAO_Contribution::updateRelatedPledge( + $action, + $pledgePaymentID, + $contribution->id, + (CRM_Utils_Array::value('option_type', $formValues) == 2) ? TRUE : FALSE, + $formValues['total_amount'], + CRM_Utils_Array::value('total_amount', $this->_defaults), + $formValues['contribution_status_id'], + CRM_Utils_Array::value('contribution_status_id', $this->_defaults) + ); + return $contribution; + } + + /** + * Assign tax calculations to contribution receipts. + * + * @param array $submittedValues + * @param int $action + * @param array $lineItem + */ + protected function invoicingPostProcessHook($submittedValues, $action, $lineItem) { + + $invoiceSettings = Civi::settings()->get('contribution_invoice_settings'); + if (!CRM_Utils_Array::value('invoicing', $invoiceSettings)) { + return; + } + $taxRate = array(); + $getTaxDetails = FALSE; + if ($action & CRM_Core_Action::ADD) { + $line = $lineItem; + } + elseif ($action & CRM_Core_Action::UPDATE) { + $line = $this->_lineItems; + } + foreach ($line as $key => $value) { + foreach ($value as $v) { + if (isset($taxRate[(string) CRM_Utils_Array::value('tax_rate', $v)])) { + $taxRate[(string) $v['tax_rate']] = $taxRate[(string) $v['tax_rate']] + CRM_Utils_Array::value('tax_amount', $v); + } + else { + if (isset($v['tax_rate'])) { + $taxRate[(string) $v['tax_rate']] = CRM_Utils_Array::value('tax_amount', $v); + $getTaxDetails = TRUE; + } + } + } + } + + if ($action & CRM_Core_Action::UPDATE) { + if (isset($submittedValues['tax_amount'])) { + $totalTaxAmount = $submittedValues['tax_amount']; + } + else { + $totalTaxAmount = $this->_values['tax_amount']; + } + $this->assign('totalTaxAmount', $totalTaxAmount); + $this->assign('dataArray', $taxRate); + } + else { + if (!empty($submittedValues['price_set_id'])) { + $this->assign('totalTaxAmount', $submittedValues['tax_amount']); + $this->assign('getTaxDetails', $getTaxDetails); + $this->assign('dataArray', $taxRate); + $this->assign('taxTerm', CRM_Utils_Array::value('tax_term', $invoiceSettings)); + } + else { + $this->assign('totalTaxAmount', CRM_Utils_Array::value('tax_amount', $submittedValues)); + } + } + } + + /** + * Calculate non deductible amount. + * + * CRM-11956 + * if non_deductible_amount exists i.e. Additional Details field set was opened [and staff typed something] - + * if non_deductible_amount does NOT exist - then calculate it depending on: + * $financialType->is_deductible and whether there is a product (premium). + * + * @param $params + * @param $formValues + * + * @return array + */ + protected function calculateNonDeductibleAmount($params, $formValues) { + if (!empty($params['non_deductible_amount'])) { + return $params['non_deductible_amount']; + } + + $financialType = new CRM_Financial_DAO_FinancialType(); + $financialType->id = $params['financial_type_id']; + $financialType->find(TRUE); + + if ($financialType->is_deductible) { + + if (isset($formValues['product_name'][0])) { + $selectProduct = $formValues['product_name'][0]; + } + // if there is a product - compare the value to the contribution amount + if (isset($selectProduct)) { + $productDAO = new CRM_Contribute_DAO_Product(); + $productDAO->id = $selectProduct; + $productDAO->find(TRUE); + // product value exceeds contribution amount + if ($params['total_amount'] < $productDAO->price) { + return $params['total_amount']; + } + // product value does NOT exceed contribution amount + else { + return $productDAO->price; + } + } + // contribution is deductible - but there is no product + else { + return '0.00'; + } + } + // contribution is NOT deductible + else { + return $params['total_amount']; + } + + return 0; + } + } diff --cc CRM/Report/Form.php index 780789cc02,023302d722..1bd2b024f6 --- a/CRM/Report/Form.php +++ b/CRM/Report/Form.php @@@ -1792,33 -1737,13 +1792,33 @@@ class CRM_Report_Form extends CRM_Core_ break; case 'mhas': + // mhas == multiple has + if ($value !== NULL && count($value) > 0) { + $sqlOP = $this->getSQLOperator($op); + foreach ($value as $key => $val) { + $val = str_replace('(', '[[.left-parenthesis.]]', $val); + $val = str_replace(')', '[[.right-parenthesis.]]', $val); + $value[$key] = $val; + } + $sp = CRM_Core_DAO::VALUE_SEPARATOR; + $clause + = "{$field['dbAlias']} REGEXP '$sp" . implode("$sp|$sp", $value) . "$sp'"; + } + break; + case 'mnot': - // mhas == multiple has + // multiple has or multiple not if ($value !== NULL && count($value) > 0) { - $value = CRM_Utils_Type::escapeAll($value, $type); - $operator = $op == 'mnot' ? 'NOT' : ''; - $regexp = "[[:cntrl:]]*" . implode('[[:>:]]*|[[:<:]]*', (array) $value) . "[[:cntrl:]]*"; - $clause = "{$field['dbAlias']} {$operator} REGEXP '{$regexp}'"; + $sqlOP = $this->getSQLOperator($op); + foreach ($value as $key => $val) { + $val = str_replace('(', '[[.left-parenthesis.]]', $val); + $val = str_replace(')', '[[.right-parenthesis.]]', $val); + $value[$key] = $val; + } + $sp = CRM_Core_DAO::VALUE_SEPARATOR; + $clause + = "( {$field['dbAlias']} NOT REGEXP '$sp" . implode("$sp|$sp", $value) . + "$sp' OR {$field['dbAlias']} IS NULL )"; } break; diff --cc tests/phpunit/WebTest/Contribute/StandaloneAddTest.php index be0b55fb7f,27bdcf162d..5e6aa10ed2 --- a/tests/phpunit/WebTest/Contribute/StandaloneAddTest.php +++ b/tests/phpunit/WebTest/Contribute/StandaloneAddTest.php @@@ -140,11 -149,10 +149,12 @@@ class WebTest_Contribute_StandaloneAddT $this->waitForElementPresent("_qf_ContributionView_cancel-bottom"); $expected = array( - 'Financial Type' => 'Donation', - 'Total Amount' => '$ 100.00', + 'Financial Type' => $financialType['name'], + 'Total Amount' => '$ 110.00', 'Contribution Status' => 'Completed', + 'Payment Method' => 'Check', + 'Check Number' => 'check #1041', + 'Paid By' => 'Cash', ); foreach ($expected as $label => $value) {