+ /*
+ * Function to record additional payment for partial and refund contributions
+ *
+ * @param integer $contributionId : is the invoice contribution id (got created after processing participant payment)
+ * @param array $trxnData : to take user provided input of transaction details.
+ * @param string $paymentType 'owed' for purpose of recording partial payments, 'refund' for purpose of recording refund payments
+ */
+ /**
+ * @param $contributionId
+ * @param $trxnsData
+ * @param string $paymentType
+ * @param null $participantId
+ *
+ * @return null|object
+ */
+ static function recordAdditionalPayment($contributionId, $trxnsData, $paymentType = 'owed', $participantId = NULL) {
+ $statusId = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
+ $getInfoOf['id'] = $contributionId;
+ $defaults = array();
+ $contributionDAO = CRM_Contribute_BAO_Contribution::retrieve($getInfoOf, $defaults, CRM_Core_DAO::$_nullArray);
+
+ if ($paymentType == 'owed') {
+ // build params for recording financial trxn entry
+ $params['contribution'] = $contributionDAO;
+ $params = array_merge($defaults, $params);
+ $params['skipLineItem'] = TRUE;
+ $params['partial_payment_total'] = $contributionDAO->total_amount;
+ $params['partial_amount_pay'] = $trxnsData['total_amount'];
+ $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis');
+
+ // record the entry
+ $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData);
+ $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+ $toFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId);
+
+ $trxnId = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId, $contributionDAO->financial_type_id);
+ if (!empty($trxnId)) {
+ $trxnId = $trxnId['trxn_id'];
+ }
+ elseif (!empty($contributionDAO->payment_instrument_id)) {
+ $trxnId = CRM_Financial_BAO_FinancialTypeAccount::getInstrumentFinancialAccount($contributionDAO->payment_instrument_id);
+ }
+ else {
+ $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name LIKE 'Asset' "));
+ $queryParams = array(1 => array($relationTypeId, 'Integer'));
+ $trxnId = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_financial_account WHERE is_default = 1 AND financial_account_type_id = %1", $queryParams);
+ }
+
+ // update statuses
+ // criteria for updates contribution total_amount == financial_trxns of partial_payments
+ $sql = "SELECT SUM(ft.total_amount) as sum_of_payments
+FROM civicrm_financial_trxn ft
+LEFT JOIN civicrm_entity_financial_trxn eft
+ ON (ft.id = eft.financial_trxn_id)
+WHERE eft.entity_table = 'civicrm_contribution'
+ AND eft.entity_id = {$contributionId}
+ AND ft.to_financial_account_id != {$toFinancialAccount}
+ AND ft.status_id = {$statusId}
+";
+ $sumOfPayments = CRM_Core_DAO::singleValueQuery($sql);
+
+ // update statuses
+ if ($contributionDAO->total_amount == $sumOfPayments) {
+ // update contribution status and
+ // clean cancel info (if any) if prev. contribution was updated in case of 'Refunded' => 'Completed'
+ $contributionDAO->contribution_status_id = $statusId;
+ $contributionDAO->cancel_date = 'null';
+ $contributionDAO->cancel_reason = NULL;
+ $netAmount = !empty($trxnsData['net_amount']) ? $trxnsData['net_amount'] : $trxnsData['total_amount'];
+ $contributionDAO->net_amount = $contributionDAO->net_amount + $netAmount;
+ $contributionDAO->save();
+
+ //Change status of financial record too
+ $financialTrxn->status_id = $statusId;
+ $financialTrxn->save();
+
+ // note : not using the self::add method,
+ // the reason because it performs 'status change' related code execution for financial records
+ // which in 'Partial Paid' => 'Completed' is not useful, instead specific financial record updates
+ // are coded below i.e. just updating financial_item status to 'Paid'
+
+ if ($participantId) {
+ // update participant status
+ $participantStatuses = CRM_Event_PseudoConstant::participantStatus();
+ $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+ foreach ($ids as $val) {
+ $participantUpdate['id'] = $val;
+ $participantUpdate['status_id'] = array_search('Registered', $participantStatuses);
+ CRM_Event_BAO_Participant::add($participantUpdate);
+ }
+ }
+
+ // update financial item statuses
+ $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id');
+ $paidStatus = array_search('Paid', $financialItemStatus);
+
+ $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
+ $sqlFinancialItemUpdate = "
+UPDATE civicrm_financial_item fi
+ LEFT JOIN civicrm_entity_financial_trxn eft
+ ON (eft.entity_id = fi.id AND eft.entity_table = 'civicrm_financial_item')
+SET status_id = {$paidStatus}
+WHERE eft.financial_trxn_id IN ({$trxnId}, {$baseTrxnId['financialTrxnId']})
+";
+ CRM_Core_DAO::executeQuery($sqlFinancialItemUpdate);
+ }
+ }
+ elseif ($paymentType == 'refund') {
+ // build params for recording financial trxn entry
+ $params['contribution'] = $contributionDAO;
+ $params = array_merge($defaults, $params);
+ $params['skipLineItem'] = TRUE;
+ $trxnsData['trxn_date'] = !empty($trxnsData['trxn_date']) ? $trxnsData['trxn_date'] : date('YmdHis');
+ $trxnsData['total_amount'] = - $trxnsData['total_amount'];
+
+ $relationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Accounts Receivable Account is' "));
+ $trxnsData['from_financial_account_id'] = CRM_Contribute_PseudoConstant::financialAccountType($contributionDAO->financial_type_id, $relationTypeId);
+ $trxnsData['status_id'] = CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name');
+ // record the entry
+ $financialTrxn = CRM_Contribute_BAO_Contribution::recordFinancialAccounts($params, $trxnsData);
+
+ // note : not using the self::add method,
+ // the reason because it performs 'status change' related code execution for financial records
+ // which in 'Pending Refund' => 'Completed' is not useful, instead specific financial record updates
+ // are coded below i.e. just updating financial_item status to 'Paid'
+ $contributionDetails = CRM_Core_DAO::setFieldValue('CRM_Contribute_BAO_Contribution', $contributionId, 'contribution_status_id', $statusId);
+
+ // add financial item entry
+ $financialItemStatus = CRM_Core_PseudoConstant::get('CRM_Financial_DAO_FinancialItem', 'status_id');
+ $getLine['entity_id'] = $contributionDAO->id;
+ $getLine['entity_table'] = 'civicrm_contribution';
+ $lineItemId = CRM_Price_BAO_LineItem::retrieve($getLine, CRM_Core_DAO::$_nullArray);
+ if (!empty($lineItemId->id)) {
+ $addFinancialEntry = array(
+ 'transaction_date' => $financialTrxn->trxn_date,
+ 'contact_id' => $contributionDAO->contact_id,
+ 'amount' => $financialTrxn->total_amount,
+ 'status_id' => array_search('Paid', $financialItemStatus),
+ 'entity_id' => $lineItemId->id,
+ 'entity_table' => 'civicrm_line_item'
+ );
+ $trxnIds['id'] = $financialTrxn->id;
+ CRM_Financial_BAO_FinancialItem::create($addFinancialEntry, NULL, $trxnIds);
+ }
+ if ($participantId) {
+ // update participant status
+ $participantStatuses = CRM_Event_PseudoConstant::participantStatus();
+ $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+ foreach ($ids as $val) {
+ $participantUpdate['id'] = $val;
+ $participantUpdate['status_id'] = array_search('Registered', $participantStatuses);
+ CRM_Event_BAO_Participant::add($participantUpdate);
+ }
+ }
+ }
+
+ // activity creation
+ if (!empty($financialTrxn)) {
+ if ($participantId) {
+ $inputParams['id'] = $participantId;
+ $values = array();
+ $ids = array();
+ $component = 'event';
+ $entityObj = CRM_Event_BAO_Participant::getValues($inputParams, $values, $ids);
+ $entityObj = $entityObj[$participantId];
+ }
+ $activityType = ($paymentType == 'refund') ? 'Refund' : 'Payment';
+
+ self::addActivityForPayment($entityObj, $financialTrxn, $activityType, $component, $contributionId);
+ }
+ return $financialTrxn;
+ }
+
+ /**
+ * @param $entityObj
+ * @param $trxnObj
+ * @param $activityType
+ * @param $component
+ * @param $contributionId
+ *
+ * @throws CRM_Core_Exception
+ */
+ static function addActivityForPayment($entityObj, $trxnObj, $activityType, $component, $contributionId) {
+ if ($component == 'event') {
+ $date = CRM_Utils_Date::isoToMysql($trxnObj->trxn_date);
+ $paymentAmount = CRM_Utils_Money::format($trxnObj->total_amount, $trxnObj->currency);
+ $eventTitle = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Event', $entityObj->event_id, 'title');
+ $subject = "{$paymentAmount} - Offline {$activityType} for {$eventTitle}";
+ $targetCid = $entityObj->contact_id;
+ // source record id would be the contribution id
+ $srcRecId = $contributionId;
+ }
+
+ // activity params
+ $activityParams = array(
+ 'source_contact_id' => $targetCid,
+ 'source_record_id' => $srcRecId,
+ 'activity_type_id' => CRM_Core_OptionGroup::getValue('activity_type',
+ $activityType,
+ 'name'
+ ),
+ 'subject' => $subject,
+ 'activity_date_time' => $date,
+ 'status_id' => CRM_Core_OptionGroup::getValue('activity_status',
+ 'Completed',
+ 'name'
+ ),
+ 'skipRecentView' => TRUE,
+ );
+
+ // create activity with target contacts
+ $session = CRM_Core_Session::singleton();
+ $id = $session->get('userID');
+ if ($id) {
+ $activityParams['source_contact_id'] = $id;
+ $activityParams['target_contact_id'][] = $targetCid;
+ }
+ CRM_Activity_BAO_Activity::create($activityParams);
+ }
+
+ /**
+ * function to get list of payments displayed by Contribute_Page_PaymentInfo
+ *
+ * @param $id
+ * @param $component
+ * @param bool $getTrxnInfo
+ * @param bool $usingLineTotal
+ *
+ * @return mixed
+ */
+ static function getPaymentInfo($id, $component, $getTrxnInfo = FALSE, $usingLineTotal = FALSE) {
+ if ($component == 'event') {
+ $entity = 'participant';
+ $entityTable = 'civicrm_participant';
+ $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_ParticipantPayment', $id, 'contribution_id', 'participant_id');
+
+ if (!$contributionId) {
+ if ($primaryParticipantId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_Participant', $id, 'registered_by_id')) {
+ $contributionId = CRM_Core_DAO::getFieldValue('CRM_Event_BAO_ParticipantPayment', $primaryParticipantId, 'contribution_id', 'participant_id');
+ $id = $primaryParticipantId;
+ }
+ }
+ }
+ $total = CRM_Core_BAO_FinancialTrxn::getBalanceTrxnAmt($contributionId);
+ $baseTrxnId = !empty($total['trxn_id']) ? $total['trxn_id'] : NULL;
+ $isBalance = NULL;
+ if ($baseTrxnId) {
+ $isBalance = TRUE;
+ }
+ else {
+ $baseTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contributionId);
+ $baseTrxnId = $baseTrxnId['financialTrxnId'];
+ $isBalance = FALSE;
+ }
+ if (empty($total) || $usingLineTotal) {
+ // for additional participants
+ if ($entityTable == 'civicrm_participant') {
+ $ids = CRM_Event_BAO_Participant::getParticipantIds($contributionId);
+ $total = 0;
+ foreach ($ids as $val) {
+ $total += CRM_Price_BAO_LineItem::getLineTotal($val, $entityTable);
+ }
+ }
+ else {
+ $total = CRM_Price_BAO_LineItem::getLineTotal($id, $entityTable);
+ }
+ }
+ else {
+ $baseTrxnId = $total['trxn_id'];
+ $total = $total['total_amount'];
+ }
+
+ $paymentBalance = CRM_Core_BAO_FinancialTrxn::getPartialPaymentWithType($id, $entity, FALSE, $total);
+ $contributionIsPayLater = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'is_pay_later');
+
+ $feeRelationTypeId = key(CRM_Core_PseudoConstant::accountOptionValues('account_relationship', NULL, " AND v.name LIKE 'Expense Account is' "));
+ $financialTypeId = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'financial_type_id');
+ $feeFinancialAccount = CRM_Contribute_PseudoConstant::financialAccountType($financialTypeId, $feeRelationTypeId);
+
+ if ($paymentBalance == 0 && $contributionIsPayLater) {
+ $paymentBalance = $total;
+ }
+
+ $info['total'] = $total;
+ $info['paid'] = $total - $paymentBalance;
+ $info['balance'] = $paymentBalance;
+ $info['id'] = $id;
+ $info['component'] = $component;
+ $info['payLater'] = $contributionIsPayLater;
+ $rows = array();
+ if ($getTrxnInfo && $baseTrxnId) {
+ // Need to exclude fee trxn rows so filter out rows where TO FINANCIAL ACCOUNT is expense account
+ $sql = "
+SELECT ft.total_amount, con.financial_type_id, ft.payment_instrument_id, ft.trxn_date, ft.trxn_id, ft.status_id, ft.check_number
+FROM civicrm_contribution con
+ LEFT JOIN civicrm_entity_financial_trxn eft ON (eft.entity_id = con.id AND eft.entity_table = 'civicrm_contribution')
+ LEFT JOIN civicrm_financial_trxn ft ON ft.id = eft.financial_trxn_id AND ft.to_financial_account_id != {$feeFinancialAccount}
+WHERE con.id = {$contributionId}
+";
+
+ // conditioned WHERE clause
+ if ($isBalance) {
+ // if balance trxn exists don't include details of it in transaction info
+ $sql .= " AND ft.id != {$baseTrxnId} ";
+ }
+ $resultDAO = CRM_Core_DAO::executeQuery($sql);
+
+ $statuses = CRM_Contribute_PseudoConstant::contributionStatus();
+ $financialTypes = CRM_Contribute_PseudoConstant::financialType();
+ while($resultDAO->fetch()) {
+ $paidByLabel = CRM_Core_PseudoConstant::getLabel('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $resultDAO->payment_instrument_id);
+ $paidByName = CRM_Core_PseudoConstant::getName('CRM_Core_BAO_FinancialTrxn', 'payment_instrument_id', $resultDAO->payment_instrument_id);
+ $val = array(
+ 'total_amount' => $resultDAO->total_amount,
+ 'financial_type' => $financialTypes[$resultDAO->financial_type_id],
+ 'payment_instrument' => $paidByLabel,
+ 'receive_date' => $resultDAO->trxn_date,
+ 'trxn_id' => $resultDAO->trxn_id,
+ 'status' => $statuses[$resultDAO->status_id],
+ );
+ if ($paidByName == 'Check') {
+ $val['check_number'] = $resultDAO->check_number;
+ }
+ $rows[] = $val;
+ }
+ $info['transaction'] = $rows;
+ }
+ return $info;
+ }
+}