Merge pull request #838 from colemanw/CRM-10573
[civicrm-core.git] / CRM / Pledge / BAO / PledgePayment.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2013
32 * $Id$
33 *
34 */
35class CRM_Pledge_BAO_PledgePayment extends CRM_Pledge_DAO_PledgePayment {
36
37 /**
38 * class constructor
39 */
40 function __construct() {
41 parent::__construct();
42 }
43
44 /**
45 * Function to get pledge payment details
46 *
47 * @param int $pledgeId pledge id
48 *
49 * @return array associated array of pledge payment details
50 * @static
51 */
52 static function getPledgePayments($pledgeId) {
53 $query = "
54SELECT civicrm_pledge_payment.id id,
55 scheduled_amount,
56 scheduled_date,
57 reminder_date,
58 reminder_count,
59 actual_amount,
60 receive_date,
61 civicrm_pledge_payment.currency,
62 civicrm_option_value.name as status,
63 civicrm_option_value.label as label,
64 civicrm_contribution.id as contribution_id
65FROM civicrm_pledge_payment
66
67LEFT JOIN civicrm_contribution ON civicrm_pledge_payment.contribution_id = civicrm_contribution.id
68LEFT JOIN civicrm_option_group ON ( civicrm_option_group.name = 'contribution_status' )
69LEFT JOIN civicrm_option_value ON ( civicrm_pledge_payment.status_id = civicrm_option_value.value AND
70 civicrm_option_group.id = civicrm_option_value.option_group_id )
71WHERE pledge_id = %1
72";
73
74 $params[1] = array($pledgeId, 'Integer');
75 $payment = CRM_Core_DAO::executeQuery($query, $params);
76
77 $paymentDetails = array();
78 while ($payment->fetch()) {
79 $paymentDetails[$payment->id]['scheduled_amount'] = $payment->scheduled_amount;
80 $paymentDetails[$payment->id]['scheduled_date'] = $payment->scheduled_date;
81 $paymentDetails[$payment->id]['reminder_date'] = $payment->reminder_date;
82 $paymentDetails[$payment->id]['reminder_count'] = $payment->reminder_count;
83 $paymentDetails[$payment->id]['total_amount'] = $payment->actual_amount;
84 $paymentDetails[$payment->id]['receive_date'] = $payment->receive_date;
85 $paymentDetails[$payment->id]['status'] = $payment->status;
86 $paymentDetails[$payment->id]['label'] = $payment->label;
87 $paymentDetails[$payment->id]['id'] = $payment->id;
88 $paymentDetails[$payment->id]['contribution_id'] = $payment->contribution_id;
89 $paymentDetails[$payment->id]['currency'] = $payment->currency;
90 }
91
92 return $paymentDetails;
93 }
94
95 static function create($params) {
96 $transaction = new CRM_Core_Transaction();
97 $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
98
99 //calculate the scheduled date for every installment
100 $now = date('Ymd') . '000000';
101 $statues = $prevScheduledDate = array();
102 $prevScheduledDate[1] = CRM_Utils_Date::processDate($params['scheduled_date']);
103
104 if (CRM_Utils_Date::overdue($prevScheduledDate[1], $now)) {
105 $statues[1] = array_search('Overdue', $contributionStatus);
106 }
107 else {
108 $statues[1] = array_search('Pending', $contributionStatus);
109 }
110
111 for ($i = 1; $i < $params['installments']; $i++) {
112 $prevScheduledDate[$i + 1] = self::calculateNextScheduledDate($params, $i);
113 if (CRM_Utils_Date::overdue($prevScheduledDate[$i + 1], $now)) {
114 $statues[$i + 1] = array_search('Overdue', $contributionStatus);
115 }
116 else {
117 $statues[$i + 1] = array_search('Pending', $contributionStatus);
118 }
119 }
120
121 if ($params['installment_amount']) {
122 $params['scheduled_amount'] = $params['installment_amount'];
123 }
124 else {
125 $params['scheduled_amount'] = round(($params['amount'] / $params['installments']), 2);
126 }
127
128 for ($i = 1; $i <= $params['installments']; $i++) {
129 //calculate the scheduled amount for every installment.
130 if ($i == $params['installments']) {
131 $params['scheduled_amount'] = $params['amount'] - ($i - 1) * $params['scheduled_amount'];
132 }
133 if (!isset($params['contribution_id']) && $params['installments'] > 1) {
134 $params['status_id'] = $statues[$i];
135 }
136
137 $params['scheduled_date'] = $prevScheduledDate[$i];
138 $payment = self::add($params);
139 if (is_a($payment, 'CRM_Core_Error')) {
140 $transaction->rollback();
141 return $payment;
142 }
143
144 // we should add contribution id to only first payment record
145 if (isset($params['contribution_id'])) {
146 unset($params['contribution_id']);
147 unset($params['actual_amount']);
148 }
149 }
150
151 //update pledge status
152 self::updatePledgePaymentStatus($params['pledge_id']);
153
154 $transaction->commit();
155 return $payment;
156 }
157
158 /**
159 * Add pledge payment
160 *
161 * @param array $params associate array of field
162 *
163 * @return pledge payment id
164 * @static
165 */
166 static function add($params) {
167 if (CRM_Utils_Array::value('id', $params)) {
168 CRM_Utils_Hook::pre('edit', 'PledgePayment', $params['id'], $params);
169 }
170 else {
171 CRM_Utils_Hook::pre('create', 'PledgePayment', NULL, $params);
172 }
173
174 $payment = new CRM_Pledge_DAO_PledgePayment();
175 $payment->copyValues($params);
176
177 // set currency for CRM-1496
178 if (!isset($payment->currency)) {
179 $config = CRM_Core_Config::singleton();
180 $payment->currency = $config->defaultCurrency;
181 }
182
183 $result = $payment->save();
184
185 if (CRM_Utils_Array::value('id', $params)) {
186 CRM_Utils_Hook::post('edit', 'PledgePayment', $payment->id, $payment);
187 }
188 else {
189 CRM_Utils_Hook::post('create', 'PledgePayment', $payment->id, $payment);
190 }
191
192
193 return $result;
194 }
195
196 /**
197 * Takes a bunch of params that are needed to match certain criteria and
198 * retrieves the relevant objects. Typically the valid params are only
199 * pledge id. We'll tweak this function to be more full featured over a period
200 * of time. This is the inverse function of create. It also stores all the retrieved
201 * values in the default array
202 *
203 * @param array $params (reference ) an assoc array of name/value pairs
204 * @param array $defaults (reference ) an assoc array to hold the flattened values
205 *
206 * @return object CRM_Pledge_BAO_PledgePayment object
207 * @access public
208 * @static
209 */
210 static function retrieve(&$params, &$defaults) {
211 $payment = new CRM_Pledge_BAO_PledgePayment;
212 $payment->copyValues($params);
213 if ($payment->find(TRUE)) {
214 CRM_Core_DAO::storeValues($payment, $defaults);
215 return $payment;
216 }
217 return NULL;
218 }
219
220 /**
221 * Delete pledge payment
222 *
223 * @param array $params associate array of field
224 *
225 * @return pledge payment id
226 * @static
227 */
228 static function del($id) {
229 $payment = new CRM_Pledge_DAO_PledgePayment();
230 $payment->id = $id;
231 if ($payment->find()) {
232 $payment->fetch();
233
234 CRM_Utils_Hook::pre('delete', 'PledgePayment', $id, $payment);
235
236 $result = $payment->delete();
237
238 CRM_Utils_Hook::post('delete', 'PledgePayment', $id, $payment);
239
240 return $result;
241 }
242 else {
243 return FALSE;
244 }
245 }
246
247 /**
248 * Function to delete all pledge payments
249 *
250 * @param int $id pledge id
251 *
252 * @access public
253 * @static
254 *
255 */
256 static function deletePayments($id) {
257 if (!CRM_Utils_Rule::positiveInteger($id)) {
258 return FALSE;
259 }
260
261 $transaction = new CRM_Core_Transaction();
262
263 $payment = new CRM_Pledge_DAO_PledgePayment();
264 $payment->pledge_id = $id;
265
266 if ($payment->find()) {
267 while ($payment->fetch()) {
268 //also delete associated contribution.
269 if ($payment->contribution_id) {
270 CRM_Contribute_BAO_Contribution::deleteContribution($payment->contribution_id);
271 }
272 $payment->delete();
273 }
274 }
275
276 $transaction->commit();
277
278 return TRUE;
279 }
280
281 /**
282 * On delete contribution record update associated pledge payment and pledge.
283 *
284 * @param int $contributionID contribution id
285 *
286 * @access public
287 * @static
288 */
289 static function resetPledgePayment($contributionID) {
290 //get all status
291 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
292
293 $transaction = new CRM_Core_Transaction();
294
295 $payment = new CRM_Pledge_DAO_PledgePayment();
296 $payment->contribution_id = $contributionID;
297 if ($payment->find(TRUE)) {
298 $payment->contribution_id = 'null';
299 $payment->status_id = array_search('Pending', $allStatus);
300 $payment->scheduled_date = NULL;
301 $payment->reminder_date = NULL;
302 $payment->scheduled_amount = $payment->actual_amount;
303 $payment->actual_amount = 'null';
304 $payment->save();
305
306 //update pledge status.
307 $pledgeID = $payment->pledge_id;
308 $pledgeStatusID = self::calculatePledgeStatus($pledgeID);
309 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'status_id', $pledgeStatusID);
310
311 $payment->free();
312 }
313
314 $transaction->commit();
315 return TRUE;
316 }
317
318 /**
319 * update Pledge Payment Status
320 *
321 * @param int $pledgeID, id of pledge
322 * @param array $paymentIDs, ids of pledge payment(s) to update
323 * @param int $paymentStatusID, payment status to set
324 * @param int $pledgeStatus, pledge status to change (if needed)
325 * @param float $actualAmount, actual amount being paid
326 * @param bool $adjustTotalAmount, is amount being paid different from scheduled amount?
327 * @param bool $isScriptUpdate, is function being called from bin script?
328 *
329 * @return int $newStatus, updated status id (or 0)
330 */
331 static function updatePledgePaymentStatus(
332 $pledgeID,
333 $paymentIDs = NULL,
334 $paymentStatusID = NULL,
335 $pledgeStatusID = NULL,
336 $actualAmount = 0,
337 $adjustTotalAmount = FALSE,
338 $isScriptUpdate = FALSE
339 ) {
340 $totalAmountClause = '';
341 $paymentContributionId = NULL;
342 $editScheduled = FALSE;
343
344 //get all statuses
345 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
346
347 // if we get do not get contribution id means we are editing the scheduled payment.
348 if (!empty($paymentIDs)) {
349 $editScheduled = FALSE;
350 $payments = implode(',', $paymentIDs);
351 $paymentContributionId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
352 $payments,
353 'contribution_id',
354 'id'
355 );
356
357 if (!$paymentContributionId) {
358 $editScheduled = TRUE;
359 }
360 }
361
362 // if payment ids are passed, we update payment table first, since payments statuses are not dependent on pledge status
363 if ((!empty($paymentIDs) || $pledgeStatusID == array_search('Cancelled', $allStatus)) && (!$editScheduled || $isScriptUpdate)) {
364 if ($pledgeStatusID == array_search('Cancelled', $allStatus)) {
365 $paymentStatusID = $pledgeStatusID;
366 }
367
368 self::updatePledgePayments($pledgeID, $paymentStatusID, $paymentIDs, $actualAmount, $paymentContributionId, $isScriptUpdate);
369 }
370 if (!empty($paymentIDs) && $actualAmount) {
371 $payments = implode(',', $paymentIDs);
372 $pledgeScheduledAmount = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
373 $payments,
374 'scheduled_amount',
375 'id'
376 );
377
378 $pledgeStatusId = self::calculatePledgeStatus($pledgeID);
379 // Actual Pledge Amount
380 $actualPledgeAmount = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
381 $pledgeID,
382 'amount',
383 'id'
384 );
385 // while editing scheduled we need to check if we are editing last pending
386 $lastPending = FALSE;
387 if (!$paymentContributionId) {
388 $checkPendingCount = self::getOldestPledgePayment($pledgeID, 2);
389 if ($checkPendingCount['count'] == 1) {
390 $lastPending = TRUE;
391 }
392 }
393
394 // check if this is the last payment and adjust the actual amount.
395 if ($pledgeStatusId && $pledgeStatusId == array_search('Completed', $allStatus) || $lastPending) {
396 // last scheduled payment
397 if ($actualAmount >= $pledgeScheduledAmount) {
398 $adjustTotalAmount = TRUE;
399 }
400 elseif (!$adjustTotalAmount) {
401 // actual amount is less than the scheduled amount, so enter new pledge payment record
402 $pledgeFrequencyUnit = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_unit', 'id');
403 $pledgeFrequencyInterval = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_interval', 'id');
404 $pledgeScheduledDate = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_date', 'id');
405 $scheduled_date = CRM_Utils_Date::processDate($pledgeScheduledDate);
406 $date['year'] = (int) substr($scheduled_date, 0, 4);
407 $date['month'] = (int) substr($scheduled_date, 4, 2);
408 $date['day'] = (int) substr($scheduled_date, 6, 2);
409 $newDate = date('YmdHis', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
410 $ScheduledDate = CRM_Utils_Date::format(CRM_Utils_Date::intervalAdd($pledgeFrequencyUnit,
411 $pledgeFrequencyInterval, $newDate
412 ));
413 $pledgeParams = array(
414 'status_id' => array_search('Pending', $allStatus),
415 'pledge_id' => $pledgeID,
416 'scheduled_amount' => ($pledgeScheduledAmount - $actualAmount),
417 'scheduled_date' => $ScheduledDate,
418 );
419 $payment = self::add($pledgeParams);
420 // while editing schedule, after adding a new pledge payemnt update the scheduled amount of the current payment
421 if (!$paymentContributionId) {
422 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
423 }
424 }
425 }
426 elseif (!$adjustTotalAmount) {
427 // not last schedule amount and also not selected to adjust Total
428 $paymentContributionId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
429 $payments,
430 'contribution_id',
431 'id'
432 );
433 self::adjustPledgePayment($pledgeID, $actualAmount, $pledgeScheduledAmount, $paymentContributionId, $payments);
434 // while editing schedule, after adding a new pledge payemnt update the scheduled amount of the current payment
435 if (!$paymentContributionId) {
436 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
437 }
438 // after adjusting all payments check if the actual amount was greater than the actual remaining amount , if so then update the total pledge amount.
439 $pledgeStatusId = self::calculatePledgeStatus($pledgeID);
440 $balanceQuery = "
441 SELECT sum( civicrm_pledge_payment.actual_amount )
442 FROM civicrm_pledge_payment
443 WHERE civicrm_pledge_payment.pledge_id = %1
444 AND civicrm_pledge_payment.status_id = 1
445 ";
446 $totalPaidParams = array(1 => array($pledgeID, 'Integer'));
447 $totalPaidAmount = CRM_Core_DAO::singleValueQuery($balanceQuery, $totalPaidParams);
448 $remainingTotalAmount = ($actualPledgeAmount - $totalPaidAmount);
449 if (($pledgeStatusId && $pledgeStatusId == array_search('Completed', $allStatus)) && (($actualAmount > $remainingTotalAmount) || ($actualAmount >= $actualPledgeAmount))) {
450 $totalAmountClause = ", civicrm_pledge.amount = {$totalPaidAmount}";
451 }
452 }
453 if ($adjustTotalAmount) {
454 $newTotalAmount = ($actualPledgeAmount + ($actualAmount - $pledgeScheduledAmount));
455 $totalAmountClause = ", civicrm_pledge.amount = {$newTotalAmount}";
456 if (!$paymentContributionId) {
457 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
458 }
459 }
460 }
461
462 $cancelDateClause = $endDateClause = NULL;
463 //update pledge and payment status if status is Completed/Cancelled.
464 if ($pledgeStatusID && $pledgeStatusID == array_search('Cancelled', $allStatus)) {
465 $paymentStatusID = $pledgeStatusID;
466 $cancelDateClause = ", civicrm_pledge.cancel_date = CURRENT_TIMESTAMP ";
467 }
468 else {
469 // get pledge status
470 $pledgeStatusID = self::calculatePledgeStatus($pledgeID);
471 }
472
473 if ($pledgeStatusID == array_search('Completed', $allStatus)) {
474 $endDateClause = ", civicrm_pledge.end_date = CURRENT_TIMESTAMP ";
475 }
476
477 //update pledge status
478 $query = "
479UPDATE civicrm_pledge
480 SET civicrm_pledge.status_id = %1
481 {$cancelDateClause} {$endDateClause} {$totalAmountClause}
482WHERE civicrm_pledge.id = %2
483";
484
485 $params = array(1 => array($pledgeStatusID, 'Integer'),
486 2 => array($pledgeID, 'Integer'),
487 );
488
489 $dao = CRM_Core_DAO::executeQuery($query, $params);
490
491 return $pledgeStatusID;
492 }
493
494 /**
495 * Calculate the base scheduled date. This function effectively 'rounds' the $params['scheduled_date'] value
496 * to the first payment date with respect to the frequency day - ie. if payments are on the 15th of the month the date returned
497 * will be the 15th of the relevant month. Then to calculate the payments you can use intervalAdd ie.
498 * CRM_Utils_Date::intervalAdd( $params['frequency_unit'], $i * ($params['frequency_interval']) , calculateBaseScheduledDate( &$params )))
499 *
500 *
501 * @param array $params
502 *
503 * @return array $newdate Next scheduled date as an array
504 * @static
505 */
506 static function calculateBaseScheduleDate(&$params) {
507 $date = array();
508 $scheduled_date = CRM_Utils_Date::processDate($params['scheduled_date']);
509 $date['year'] = (int) substr($scheduled_date, 0, 4);
510 $date['month'] = (int) substr($scheduled_date, 4, 2);
511 $date['day'] = (int) substr($scheduled_date, 6, 2);
512 //calculation of schedule date according to frequency day of period
513 //frequency day is not applicable for daily installments
514 if ($params['frequency_unit'] != 'day' && $params['frequency_unit'] != 'year') {
515 if ($params['frequency_unit'] != 'week') {
516
517 //for month use day of next month as next payment date
518 $date['day'] = $params['frequency_day'];
519 }
520 elseif ($params['frequency_unit'] == 'week') {
521
522 //for week calculate day of week ie. Sunday,Monday etc. as next payment date
523 $dayOfWeek = date('w', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
524 $frequencyDay = $params['frequency_day'] - $dayOfWeek;
525
526 $scheduleDate = explode("-", date('n-j-Y', mktime(0, 0, 0, $date['month'],
527 $date['day'] + $frequencyDay, $date['year']
528 )));
529 $date['month'] = $scheduleDate[0];
530 $date['day'] = $scheduleDate[1];
531 $date['year'] = $scheduleDate[2];
532 }
533 }
534 $newdate = date('YmdHis', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
535 return $newdate;
536 }
537
538 /**
539 * Calculate next scheduled pledge payment date. Function calculates next pledge payment date.
540 *
541 * @param array params - must include frequency unit & frequency interval
542 * @param int paymentNo number of payment in sequence (e.g. 1 for first calculated payment (treat initial payment as 0)
543 * @param datestring basePaymentDate - date to calculate payments from. This would normally be the
544 * first day of the pledge (default) & is calculated off the 'scheduled date' param. Returned date will
545 * be equal to basePaymentDate normalised to fit the 'pledge pattern' + number of installments
546 *
547 * @return formatted date
548 *
549 */
550 static function calculateNextScheduledDate(&$params, $paymentNo, $basePaymentDate = NULL) {
551 if (!$basePaymentDate) {
552 $basePaymentDate = self::calculateBaseScheduleDate($params);
553 }
554 return CRM_Utils_Date::format(
555 CRM_Utils_Date::intervalAdd(
556 $params['frequency_unit'],
557 $paymentNo * ($params['frequency_interval']),
558 $basePaymentDate
559 )
560 );
561 }
562
563 /**
564 * Calculate the pledge status
565 *
566 * @param int $pledgeId pledge id
567 *
568 * @return int $statusId calculated status id of pledge
569 * @static
570 */
571 static function calculatePledgeStatus($pledgeId) {
572 $paymentStatusTypes = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
573
574 //retrieve all pledge payments for this particular pledge
575 $allPledgePayments = $allStatus = array();
576 $returnProperties = array('status_id');
577 CRM_Core_DAO::commonRetrieveAll('CRM_Pledge_DAO_PledgePayment', 'pledge_id', $pledgeId, $allPledgePayments, $returnProperties);
578
579 // build pledge payment statuses
580 foreach ($allPledgePayments as $key => $value) {
581 $allStatus[$value['id']] = $paymentStatusTypes[$value['status_id']];
582 }
583
584 if (array_search('Overdue', $allStatus)) {
585 $statusId = array_search('Overdue', $paymentStatusTypes);
586 }
587 elseif (array_search('Completed', $allStatus)) {
588 if (count(array_count_values($allStatus)) == 1) {
589 $statusId = array_search('Completed', $paymentStatusTypes);
590 }
591 else {
592 $statusId = array_search('In Progress', $paymentStatusTypes);
593 }
594 }
595 else {
596 $statusId = array_search('Pending', $paymentStatusTypes);
597 }
598
599 return $statusId;
600 }
601
602 /**
603 * Function to update pledge payment table
604 *
605 * @param int $pledgeId pledge id
606 * @param array $paymentIds payment ids to be updated
607 * @param int $paymentStatusId payment status id to set
608 * @param float $actualAmount, actual amount being paid
609 * @param int $contributionId, Id of associated contribution when payment is recorded
610 * @param bool $isScriptUpdate, is function being called from bin script?
611 * @static
612 */
613 static function updatePledgePayments($pledgeId,
614 $paymentStatusId,
615 $paymentIds = NULL,
616 $actualAmount = 0,
617 $contributionId = NULL,
618 $isScriptUpdate = FALSE
619 ) {
620 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
621 $paymentClause = NULL;
622 if (!empty($paymentIds)) {
623 $payments = implode(',', $paymentIds);
624 $paymentClause = " AND civicrm_pledge_payment.id IN ( {$payments} )";
625 }
626 $actualAmountClause = NULL;
627 $contributionIdClause = NULL;
628 if (isset($contributionId) && !$isScriptUpdate) {
629 $contributionIdClause = ", civicrm_pledge_payment.contribution_id = {$contributionId}";
630 $actualAmountClause = ", civicrm_pledge_payment.actual_amount = {$actualAmount}";
631 }
632
633 $query = "
634UPDATE civicrm_pledge_payment
635SET civicrm_pledge_payment.status_id = {$paymentStatusId}
636 {$actualAmountClause} {$contributionIdClause}
637WHERE civicrm_pledge_payment.pledge_id = %1
638 {$paymentClause}
639";
640
641 //get all status
642 $params = array(1 => array($pledgeId, 'Integer'));
643
644 $dao = CRM_Core_DAO::executeQuery($query, $params);
645 }
646
647 /**
648 * Function to update pledge payment table when reminder is sent
649 *
650 * @param int $paymentId payment id
651 *
652 * @static
653 */
654 static function updateReminderDetails($paymentId) {
655 $query = "
656UPDATE civicrm_pledge_payment
657SET civicrm_pledge_payment.reminder_date = CURRENT_TIMESTAMP,
658 civicrm_pledge_payment.reminder_count = civicrm_pledge_payment.reminder_count + 1
659WHERE civicrm_pledge_payment.id = {$paymentId}
660";
661 $dao = CRM_Core_DAO::executeQuery($query);
662 }
663
664 /**
665 * Function to get oldest pending or in progress pledge payments
666 *
667 * @param int $pledgeID pledge id
668 *
669 * @return array associated array of pledge details
670 * @static
671 */
672 static function getOldestPledgePayment($pledgeID, $limit = 1) {
673 //get pending / overdue statuses
674 $pledgeStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
675
676 //get pending and overdue payments
677 $status[] = array_search('Pending', $pledgeStatuses);
678 $status[] = array_search('Overdue', $pledgeStatuses);
679
680 $statusClause = " IN (" . implode(',', $status) . ")";
681
682 $query = "
683SELECT civicrm_pledge_payment.id id, civicrm_pledge_payment.scheduled_amount amount, civicrm_pledge_payment.currency
684FROM civicrm_pledge, civicrm_pledge_payment
685WHERE civicrm_pledge.id = civicrm_pledge_payment.pledge_id
686 AND civicrm_pledge_payment.status_id {$statusClause}
687 AND civicrm_pledge.id = %1
688ORDER BY civicrm_pledge_payment.scheduled_date ASC
689LIMIT 0, %2
690";
691
692 $params[1] = array($pledgeID, 'Integer');
693 $params[2] = array($limit, 'Integer');
694 $payment = CRM_Core_DAO::executeQuery($query, $params);
695 $count = 1;
696 $paymentDetails = array();
697 while ($payment->fetch()) {
698 $paymentDetails[] = array(
699 'id' => $payment->id,
700 'amount' => $payment->amount,
701 'currency' => $payment->currency,
702 'count' => $count,
703 );
704 $count++;
705 }
706 return end($paymentDetails);
707 }
708
709 static function adjustPledgePayment($pledgeID, $actualAmount, $pledgeScheduledAmount, $paymentContributionId = NULL, $pPaymentId = NULL) {
710 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
711 $oldestPayment = self::getOldestPledgePayment($pledgeID);
712 if (!$paymentContributionId) {
713 // means we are editing payment scheduled payment, so get the second pending to update.
714 $oldestPayment = self::getOldestPledgePayment($pledgeID, 2);
715 if (($oldestPayment['count'] != 1) && ($oldestPayment['id'] == $pPaymentId)) {
716 $oldestPayment = self::getOldestPledgePayment($pledgeID);
717 }
718 }
719
720 if ($oldestPayment) {
721 // not the last scheduled payment and the actual amount is less than the expected , add it to oldest pending.
722 if (($actualAmount != $pledgeScheduledAmount) && (($actualAmount < $pledgeScheduledAmount) || (($actualAmount - $pledgeScheduledAmount) < $oldestPayment['amount']))) {
723 $oldScheduledAmount = $oldestPayment['amount'];
724 $newScheduledAmount = $oldScheduledAmount + ($pledgeScheduledAmount - $actualAmount);
725 //store new amount in oldest pending payment record.
726 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $oldestPayment['id'], 'scheduled_amount', $newScheduledAmount);
727 }
728 elseif (($actualAmount > $pledgeScheduledAmount) && (($actualAmount - $pledgeScheduledAmount) >= $oldestPayment['amount'])) {
729 // here the actual amount is greater than expected and also greater than the next installment amount, so update the next installment as complete and again add it to next subsequent pending payment
730 // set the actual amount of the next pending to '0', set contribution Id to current contribution Id and status as completed
731 $paymentId = array($oldestPayment['id']);
732 self::updatePledgePayments($pledgeID, array_search('Completed', $allStatus), $paymentId, 0, $paymentContributionId);
733 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $oldestPayment['id'], 'scheduled_amount', 0, 'id');
734 $oldestPayment = self::getOldestPledgePayment($pledgeID);
735 if (!$paymentContributionId) {
736 // means we are editing payment scheduled payment.
737 $oldestPaymentAmount = self::getOldestPledgePayment($pledgeID, 2);
738 }
739 $newActualAmount = ($actualAmount - $pledgeScheduledAmount);
740 $newPledgeScheduledAmount = $oldestPayment['amount'];
741 if (!$paymentContributionId) {
742 $newActualAmount = ($actualAmount - $pledgeScheduledAmount);
743 $newPledgeScheduledAmount = $oldestPaymentAmount['amount'];
744 // means we are editing payment scheduled payment, so update scheduled amount.
745 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment',
746 $oldestPaymentAmount['id'],
747 'scheduled_amount',
748 $newActualAmount
749 );
750 }
751 if ($newActualAmount > 0) {
752 self::adjustPledgePayment($pledgeID, $newActualAmount, $newPledgeScheduledAmount, $paymentContributionId);
753 }
754 }
755 }
756 }
757}
758