CRM-20913 use pledge option group for pledge payments
[civicrm-core.git] / CRM / Pledge / BAO / PledgePayment.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
7e9e8871 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
0f03f337 6 | Copyright CiviCRM LLC (c) 2004-2017 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
0f03f337 31 * @copyright CiviCRM LLC (c) 2004-2017
6a488035
TO
32 */
33class CRM_Pledge_BAO_PledgePayment extends CRM_Pledge_DAO_PledgePayment {
34
35 /**
fe482240 36 * Class constructor.
6a488035 37 */
00be9182 38 public function __construct() {
6a488035
TO
39 parent::__construct();
40 }
41
42 /**
fe482240 43 * Get pledge payment details.
6a488035 44 *
3a1617b6
TO
45 * @param int $pledgeId
46 * Pledge id.
6a488035 47 *
a6c01b45
CW
48 * @return array
49 * associated array of pledge payment details
6a488035 50 */
00be9182 51 public static function getPledgePayments($pledgeId) {
6a488035
TO
52 $query = "
53SELECT civicrm_pledge_payment.id id,
54 scheduled_amount,
55 scheduled_date,
56 reminder_date,
57 reminder_count,
58 actual_amount,
59 receive_date,
a9ae3b2c 60 civicrm_pledge_payment.currency,
6a488035
TO
61 civicrm_option_value.name as status,
62 civicrm_option_value.label as label,
63 civicrm_contribution.id as contribution_id
64FROM civicrm_pledge_payment
65
66LEFT JOIN civicrm_contribution ON civicrm_pledge_payment.contribution_id = civicrm_contribution.id
67LEFT JOIN civicrm_option_group ON ( civicrm_option_group.name = 'contribution_status' )
68LEFT JOIN civicrm_option_value ON ( civicrm_pledge_payment.status_id = civicrm_option_value.value AND
69 civicrm_option_group.id = civicrm_option_value.option_group_id )
70WHERE pledge_id = %1
71";
72
73 $params[1] = array($pledgeId, 'Integer');
74 $payment = CRM_Core_DAO::executeQuery($query, $params);
75
76 $paymentDetails = array();
77 while ($payment->fetch()) {
78 $paymentDetails[$payment->id]['scheduled_amount'] = $payment->scheduled_amount;
79 $paymentDetails[$payment->id]['scheduled_date'] = $payment->scheduled_date;
80 $paymentDetails[$payment->id]['reminder_date'] = $payment->reminder_date;
81 $paymentDetails[$payment->id]['reminder_count'] = $payment->reminder_count;
82 $paymentDetails[$payment->id]['total_amount'] = $payment->actual_amount;
83 $paymentDetails[$payment->id]['receive_date'] = $payment->receive_date;
84 $paymentDetails[$payment->id]['status'] = $payment->status;
85 $paymentDetails[$payment->id]['label'] = $payment->label;
86 $paymentDetails[$payment->id]['id'] = $payment->id;
87 $paymentDetails[$payment->id]['contribution_id'] = $payment->contribution_id;
88 $paymentDetails[$payment->id]['currency'] = $payment->currency;
89 }
90
91 return $paymentDetails;
92 }
93
ffd93213 94 /**
c490a46a 95 * @param array $params
ffd93213
EM
96 *
97 * @return pledge
98 */
00be9182 99 public static function create($params) {
6a488035 100 $transaction = new CRM_Core_Transaction();
74edda99 101 $overdueStatusID = CRM_Core_PseudoConstant::getKey('CRM_Pledge_BAO_PledgePayment', 'status_id', 'Overdue');
102 $pendingStatusId = CRM_Core_PseudoConstant::getKey('CRM_Pledge_BAO_PledgePayment', 'status_id', 'Pending');
6a488035
TO
103
104 //calculate the scheduled date for every installment
105 $now = date('Ymd') . '000000';
106 $statues = $prevScheduledDate = array();
107 $prevScheduledDate[1] = CRM_Utils_Date::processDate($params['scheduled_date']);
108
109 if (CRM_Utils_Date::overdue($prevScheduledDate[1], $now)) {
74edda99 110 $statues[1] = $overdueStatusID;
6a488035
TO
111 }
112 else {
74edda99 113 $statues[1] = $pendingStatusId;
6a488035
TO
114 }
115
116 for ($i = 1; $i < $params['installments']; $i++) {
117 $prevScheduledDate[$i + 1] = self::calculateNextScheduledDate($params, $i);
118 if (CRM_Utils_Date::overdue($prevScheduledDate[$i + 1], $now)) {
74edda99 119 $statues[$i + 1] = $overdueStatusID;
6a488035
TO
120 }
121 else {
74edda99 122 $statues[$i + 1] = $pendingStatusId;
6a488035
TO
123 }
124 }
125
126 if ($params['installment_amount']) {
127 $params['scheduled_amount'] = $params['installment_amount'];
128 }
129 else {
130 $params['scheduled_amount'] = round(($params['amount'] / $params['installments']), 2);
131 }
132
133 for ($i = 1; $i <= $params['installments']; $i++) {
cc28438b 134 // calculate the scheduled amount for every installment.
6a488035
TO
135 if ($i == $params['installments']) {
136 $params['scheduled_amount'] = $params['amount'] - ($i - 1) * $params['scheduled_amount'];
137 }
138 if (!isset($params['contribution_id']) && $params['installments'] > 1) {
139 $params['status_id'] = $statues[$i];
140 }
141
142 $params['scheduled_date'] = $prevScheduledDate[$i];
143 $payment = self::add($params);
144 if (is_a($payment, 'CRM_Core_Error')) {
145 $transaction->rollback();
146 return $payment;
147 }
148
149 // we should add contribution id to only first payment record
150 if (isset($params['contribution_id'])) {
151 unset($params['contribution_id']);
152 unset($params['actual_amount']);
153 }
154 }
155
cc28438b 156 // update pledge status
6a488035
TO
157 self::updatePledgePaymentStatus($params['pledge_id']);
158
159 $transaction->commit();
160 return $payment;
161 }
162
163 /**
fe482240 164 * Add pledge payment.
6a488035 165 *
3a1617b6
TO
166 * @param array $params
167 * Associate array of field.
6a488035 168 *
906e6120 169 * @return CRM_Pledge_DAO_PledgePayment
72b3a70c 170 * pledge payment id
6a488035 171 */
00be9182 172 public static function add($params) {
a7488080 173 if (!empty($params['id'])) {
6a488035
TO
174 CRM_Utils_Hook::pre('edit', 'PledgePayment', $params['id'], $params);
175 }
176 else {
177 CRM_Utils_Hook::pre('create', 'PledgePayment', NULL, $params);
178 }
179
180 $payment = new CRM_Pledge_DAO_PledgePayment();
181 $payment->copyValues($params);
182
183 // set currency for CRM-1496
184 if (!isset($payment->currency)) {
906e6120 185 $payment->currency = CRM_Core_Config::singleton()->defaultCurrency;
6a488035
TO
186 }
187
188 $result = $payment->save();
189
a7488080 190 if (!empty($params['id'])) {
6a488035
TO
191 CRM_Utils_Hook::post('edit', 'PledgePayment', $payment->id, $payment);
192 }
193 else {
194 CRM_Utils_Hook::post('create', 'PledgePayment', $payment->id, $payment);
195 }
196
6a488035
TO
197 return $result;
198 }
199
200 /**
fe482240
EM
201 * Retrieve DB object based on input parameters.
202 *
203 * It also stores all the retrieved values in the default array.
6a488035 204 *
3a1617b6
TO
205 * @param array $params
206 * (reference ) an assoc array of name/value pairs.
207 * @param array $defaults
208 * (reference ) an assoc array to hold the flattened values.
6a488035 209 *
16b10e64 210 * @return CRM_Pledge_BAO_PledgePayment
6a488035 211 */
00be9182 212 public static function retrieve(&$params, &$defaults) {
317fceb4 213 $payment = new CRM_Pledge_BAO_PledgePayment();
6a488035
TO
214 $payment->copyValues($params);
215 if ($payment->find(TRUE)) {
216 CRM_Core_DAO::storeValues($payment, $defaults);
217 return $payment;
218 }
219 return NULL;
220 }
221
222 /**
fe482240 223 * Delete pledge payment.
6a488035 224 *
100fef9d 225 * @param int $id
6a488035 226 *
a6c01b45
CW
227 * @return int
228 * pledge payment id
6a488035 229 */
00be9182 230 public static function del($id) {
6a488035
TO
231 $payment = new CRM_Pledge_DAO_PledgePayment();
232 $payment->id = $id;
233 if ($payment->find()) {
234 $payment->fetch();
235
236 CRM_Utils_Hook::pre('delete', 'PledgePayment', $id, $payment);
237
238 $result = $payment->delete();
239
240 CRM_Utils_Hook::post('delete', 'PledgePayment', $id, $payment);
241
242 return $result;
243 }
244 else {
245 return FALSE;
246 }
247 }
248
249 /**
fe482240 250 * Delete all pledge payments.
6a488035 251 *
3a1617b6
TO
252 * @param int $id
253 * Pledge id.
6a488035 254 *
77b97be7 255 * @return bool
6a488035 256 */
00be9182 257 public static function deletePayments($id) {
6a488035
TO
258 if (!CRM_Utils_Rule::positiveInteger($id)) {
259 return FALSE;
260 }
261
262 $transaction = new CRM_Core_Transaction();
263
264 $payment = new CRM_Pledge_DAO_PledgePayment();
265 $payment->pledge_id = $id;
266
267 if ($payment->find()) {
268 while ($payment->fetch()) {
269 //also delete associated contribution.
270 if ($payment->contribution_id) {
271 CRM_Contribute_BAO_Contribution::deleteContribution($payment->contribution_id);
272 }
7eb6e796 273 self::del($payment->id);
6a488035
TO
274 }
275 }
276
277 $transaction->commit();
278
279 return TRUE;
280 }
281
282 /**
283 * On delete contribution record update associated pledge payment and pledge.
284 *
3a1617b6
TO
285 * @param int $contributionID
286 * Contribution id.
6a488035 287 *
77b97be7 288 * @return bool
6a488035 289 */
00be9182 290 public static function resetPledgePayment($contributionID) {
cc28438b 291 // get all status
6a488035
TO
292 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
293
294 $transaction = new CRM_Core_Transaction();
295
296 $payment = new CRM_Pledge_DAO_PledgePayment();
297 $payment->contribution_id = $contributionID;
298 if ($payment->find(TRUE)) {
299 $payment->contribution_id = 'null';
300 $payment->status_id = array_search('Pending', $allStatus);
301 $payment->scheduled_date = NULL;
302 $payment->reminder_date = NULL;
303 $payment->scheduled_amount = $payment->actual_amount;
304 $payment->actual_amount = 'null';
305 $payment->save();
306
307 //update pledge status.
308 $pledgeID = $payment->pledge_id;
309 $pledgeStatusID = self::calculatePledgeStatus($pledgeID);
310 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'status_id', $pledgeStatusID);
311
312 $payment->free();
313 }
314
315 $transaction->commit();
316 return TRUE;
317 }
318
319 /**
fe482240 320 * Update Pledge Payment Status.
6a488035 321 *
3a1617b6
TO
322 * @param int $pledgeID
323 * , id of pledge.
324 * @param array $paymentIDs
325 * , ids of pledge payment(s) to update.
326 * @param int $paymentStatusID
327 * , payment status to set.
328 * @param int $pledgeStatusID
329 * Pledge status to change (if needed).
fd31fa4c 330 * @param float|int $actualAmount , actual amount being paid
3a1617b6
TO
331 * @param bool $adjustTotalAmount
332 * , is amount being paid different from scheduled amount?.
333 * @param bool $isScriptUpdate
334 * , is function being called from bin script?.
6a488035 335 *
a6c01b45
CW
336 * @return int
337 * $newStatus, updated status id (or 0)
6a488035 338 */
317fceb4 339 public static function updatePledgePaymentStatus(
6a488035 340 $pledgeID,
64041b14 341 $paymentIDs = NULL,
342 $paymentStatusID = NULL,
343 $pledgeStatusID = NULL,
344 $actualAmount = 0,
6a488035 345 $adjustTotalAmount = FALSE,
64041b14 346 $isScriptUpdate = FALSE
6a488035
TO
347 ) {
348 $totalAmountClause = '';
349 $paymentContributionId = NULL;
350 $editScheduled = FALSE;
351
cc28438b 352 // get all statuses
e7212d86
JP
353 $allStatus = CRM_Core_OptionGroup::values('pledge_status',
354 FALSE, FALSE, FALSE, NULL, 'name', TRUE
355 );
6a488035
TO
356
357 // if we get do not get contribution id means we are editing the scheduled payment.
358 if (!empty($paymentIDs)) {
359 $editScheduled = FALSE;
360 $payments = implode(',', $paymentIDs);
361 $paymentContributionId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
362 $payments,
363 'contribution_id',
364 'id'
365 );
366
367 if (!$paymentContributionId) {
368 $editScheduled = TRUE;
369 }
370 }
371
372 // if payment ids are passed, we update payment table first, since payments statuses are not dependent on pledge status
373 if ((!empty($paymentIDs) || $pledgeStatusID == array_search('Cancelled', $allStatus)) && (!$editScheduled || $isScriptUpdate)) {
374 if ($pledgeStatusID == array_search('Cancelled', $allStatus)) {
375 $paymentStatusID = $pledgeStatusID;
376 }
377
378 self::updatePledgePayments($pledgeID, $paymentStatusID, $paymentIDs, $actualAmount, $paymentContributionId, $isScriptUpdate);
379 }
380 if (!empty($paymentIDs) && $actualAmount) {
381 $payments = implode(',', $paymentIDs);
382 $pledgeScheduledAmount = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
383 $payments,
384 'scheduled_amount',
385 'id'
386 );
387
388 $pledgeStatusId = self::calculatePledgeStatus($pledgeID);
389 // Actual Pledge Amount
390 $actualPledgeAmount = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge',
391 $pledgeID,
392 'amount',
393 'id'
394 );
cc28438b 395 // while editing scheduled we need to check if we are editing last pending
6a488035
TO
396 $lastPending = FALSE;
397 if (!$paymentContributionId) {
398 $checkPendingCount = self::getOldestPledgePayment($pledgeID, 2);
399 if ($checkPendingCount['count'] == 1) {
400 $lastPending = TRUE;
401 }
402 }
403
404 // check if this is the last payment and adjust the actual amount.
405 if ($pledgeStatusId && $pledgeStatusId == array_search('Completed', $allStatus) || $lastPending) {
406 // last scheduled payment
407 if ($actualAmount >= $pledgeScheduledAmount) {
64041b14 408 $adjustTotalAmount = TRUE;
409 }
6a488035
TO
410 elseif (!$adjustTotalAmount) {
411 // actual amount is less than the scheduled amount, so enter new pledge payment record
412 $pledgeFrequencyUnit = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_unit', 'id');
413 $pledgeFrequencyInterval = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_interval', 'id');
414 $pledgeScheduledDate = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_date', 'id');
415 $scheduled_date = CRM_Utils_Date::processDate($pledgeScheduledDate);
416 $date['year'] = (int) substr($scheduled_date, 0, 4);
417 $date['month'] = (int) substr($scheduled_date, 4, 2);
418 $date['day'] = (int) substr($scheduled_date, 6, 2);
419 $newDate = date('YmdHis', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
420 $ScheduledDate = CRM_Utils_Date::format(CRM_Utils_Date::intervalAdd($pledgeFrequencyUnit,
64041b14 421 $pledgeFrequencyInterval, $newDate
422 ));
6a488035
TO
423 $pledgeParams = array(
424 'status_id' => array_search('Pending', $allStatus),
425 'pledge_id' => $pledgeID,
426 'scheduled_amount' => ($pledgeScheduledAmount - $actualAmount),
427 'scheduled_date' => $ScheduledDate,
428 );
429 $payment = self::add($pledgeParams);
430 // while editing schedule, after adding a new pledge payemnt update the scheduled amount of the current payment
431 if (!$paymentContributionId) {
432 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
433 }
434 }
64041b14 435 }
6a488035
TO
436 elseif (!$adjustTotalAmount) {
437 // not last schedule amount and also not selected to adjust Total
438 $paymentContributionId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment',
439 $payments,
440 'contribution_id',
441 'id'
442 );
a9ae3b2c 443 self::adjustPledgePayment($pledgeID, $actualAmount, $pledgeScheduledAmount, $paymentContributionId, $payments, $paymentStatusID);
6a488035
TO
444 // while editing schedule, after adding a new pledge payemnt update the scheduled amount of the current payment
445 if (!$paymentContributionId) {
446 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
447 }
448 // after adjusting all payments check if the actual amount was greater than the actual remaining amount , if so then update the total pledge amount.
449 $pledgeStatusId = self::calculatePledgeStatus($pledgeID);
450 $balanceQuery = "
451 SELECT sum( civicrm_pledge_payment.actual_amount )
452 FROM civicrm_pledge_payment
453 WHERE civicrm_pledge_payment.pledge_id = %1
454 AND civicrm_pledge_payment.status_id = 1
455 ";
456 $totalPaidParams = array(1 => array($pledgeID, 'Integer'));
457 $totalPaidAmount = CRM_Core_DAO::singleValueQuery($balanceQuery, $totalPaidParams);
458 $remainingTotalAmount = ($actualPledgeAmount - $totalPaidAmount);
459 if (($pledgeStatusId && $pledgeStatusId == array_search('Completed', $allStatus)) && (($actualAmount > $remainingTotalAmount) || ($actualAmount >= $actualPledgeAmount))) {
460 $totalAmountClause = ", civicrm_pledge.amount = {$totalPaidAmount}";
461 }
462 }
463 if ($adjustTotalAmount) {
464 $newTotalAmount = ($actualPledgeAmount + ($actualAmount - $pledgeScheduledAmount));
465 $totalAmountClause = ", civicrm_pledge.amount = {$newTotalAmount}";
466 if (!$paymentContributionId) {
467 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $payments, 'scheduled_amount', $actualAmount);
468 }
469 }
470 }
471
472 $cancelDateClause = $endDateClause = NULL;
cc28438b 473 // update pledge and payment status if status is Completed/Cancelled.
6a488035
TO
474 if ($pledgeStatusID && $pledgeStatusID == array_search('Cancelled', $allStatus)) {
475 $paymentStatusID = $pledgeStatusID;
476 $cancelDateClause = ", civicrm_pledge.cancel_date = CURRENT_TIMESTAMP ";
477 }
478 else {
479 // get pledge status
480 $pledgeStatusID = self::calculatePledgeStatus($pledgeID);
481 }
482
483 if ($pledgeStatusID == array_search('Completed', $allStatus)) {
484 $endDateClause = ", civicrm_pledge.end_date = CURRENT_TIMESTAMP ";
485 }
486
cc28438b 487 // update pledge status
6a488035
TO
488 $query = "
489UPDATE civicrm_pledge
490 SET civicrm_pledge.status_id = %1
491 {$cancelDateClause} {$endDateClause} {$totalAmountClause}
492WHERE civicrm_pledge.id = %2
493";
494
64041b14 495 $params = array(
496 1 => array($pledgeStatusID, 'Integer'),
6a488035
TO
497 2 => array($pledgeID, 'Integer'),
498 );
499
500 $dao = CRM_Core_DAO::executeQuery($query, $params);
501
502 return $pledgeStatusID;
503 }
504
505 /**
506 * Calculate the base scheduled date. This function effectively 'rounds' the $params['scheduled_date'] value
507 * to the first payment date with respect to the frequency day - ie. if payments are on the 15th of the month the date returned
508 * will be the 15th of the relevant month. Then to calculate the payments you can use intervalAdd ie.
509 * CRM_Utils_Date::intervalAdd( $params['frequency_unit'], $i * ($params['frequency_interval']) , calculateBaseScheduledDate( &$params )))
510 *
6a488035
TO
511 * @param array $params
512 *
a6c01b45
CW
513 * @return array
514 * Next scheduled date as an array
6a488035 515 */
7e66081a 516 public static function calculateBaseScheduleDate(&$params) {
64041b14 517 $date = array();
6a488035 518 $scheduled_date = CRM_Utils_Date::processDate($params['scheduled_date']);
64041b14 519 $date['year'] = (int) substr($scheduled_date, 0, 4);
520 $date['month'] = (int) substr($scheduled_date, 4, 2);
521 $date['day'] = (int) substr($scheduled_date, 6, 2);
cc28438b
SB
522 // calculation of schedule date according to frequency day of period
523 // frequency day is not applicable for daily installments
6a488035
TO
524 if ($params['frequency_unit'] != 'day' && $params['frequency_unit'] != 'year') {
525 if ($params['frequency_unit'] != 'week') {
921bb3a9 526 // CRM-18316: To calculate pledge scheduled dates at the end of a month.
6a488035 527 $date['day'] = $params['frequency_day'];
7e66081a 528 $lastDayOfMonth = date('t', mktime(0, 0, 0, $date['month'], 1, $date['year']));
921bb3a9
WA
529 if ($lastDayOfMonth < $date['day']) {
530 $date['day'] = $lastDayOfMonth;
531 }
6a488035
TO
532 }
533 elseif ($params['frequency_unit'] == 'week') {
534
cc28438b 535 // for week calculate day of week ie. Sunday,Monday etc. as next payment date
6a488035
TO
536 $dayOfWeek = date('w', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
537 $frequencyDay = $params['frequency_day'] - $dayOfWeek;
538
539 $scheduleDate = explode("-", date('n-j-Y', mktime(0, 0, 0, $date['month'],
64041b14 540 $date['day'] + $frequencyDay, $date['year']
541 )));
6a488035 542 $date['month'] = $scheduleDate[0];
64041b14 543 $date['day'] = $scheduleDate[1];
544 $date['year'] = $scheduleDate[2];
6a488035
TO
545 }
546 }
547 $newdate = date('YmdHis', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
548 return $newdate;
549 }
550
551 /**
552 * Calculate next scheduled pledge payment date. Function calculates next pledge payment date.
553 *
72b3a70c
CW
554 * @param array $params
555 * must include frequency unit & frequency interval
556 * @param int $paymentNo
557 * number of payment in sequence (e.g. 1 for first calculated payment (treat initial payment as 0)
558 * @param string $basePaymentDate
559 * date to calculate payments from. This would normally be the
560 * first day of the pledge (default) & is calculated off the 'scheduled date' param. Returned date will
561 * be equal to basePaymentDate normalised to fit the 'pledge pattern' + number of installments
6a488035 562 *
72b3a70c
CW
563 * @return string
564 * formatted date
6a488035 565 */
00be9182 566 public static function calculateNextScheduledDate(&$params, $paymentNo, $basePaymentDate = NULL) {
7e66081a 567 $interval = $paymentNo * ($params['frequency_interval']);
6a488035 568 if (!$basePaymentDate) {
7e66081a 569 $basePaymentDate = self::calculateBaseScheduleDate($params);
570 }
571
572 //CRM-18316 - change $basePaymentDate for the end dates of the month eg: 29, 30 or 31.
573 if ($params['frequency_unit'] == 'month' && in_array($params['frequency_day'], array(29, 30, 31))) {
574 $frequency = $params['frequency_day'];
575 extract(date_parse($basePaymentDate));
576 $lastDayOfMonth = date('t', mktime($hour, $minute, $second, $month + $interval, 1, $year));
577 // Take the last day in case the current month is Feb or frequency_day is set to 31.
578 if (in_array($lastDayOfMonth, array(28, 29)) || $frequency == 31) {
579 $frequency = 0;
580 $interval++;
581 }
582 $basePaymentDate = array(
583 'M' => $month,
584 'd' => $frequency,
585 'Y' => $year,
586 );
6a488035 587 }
7e66081a 588
6a488035
TO
589 return CRM_Utils_Date::format(
590 CRM_Utils_Date::intervalAdd(
591 $params['frequency_unit'],
7e66081a 592 $interval,
6a488035
TO
593 $basePaymentDate
594 )
595 );
596 }
597
598 /**
fe482240 599 * Calculate the pledge status.
6a488035 600 *
3a1617b6
TO
601 * @param int $pledgeId
602 * Pledge id.
6a488035 603 *
a6c01b45
CW
604 * @return int
605 * $statusId calculated status id of pledge
6a488035 606 */
00be9182 607 public static function calculatePledgeStatus($pledgeId) {
74edda99 608 $paymentStatusTypes = CRM_Pledge_BAO_PledgePayment::buildOptions('status_id');
609 $pledgeStatusTypes = CRM_Pledge_BAO_Pledge::buildOptions('status_id');
6a488035 610
2c0f8c67 611 //return if the pledge is cancelled.
74edda99 612 $currentPledgeStatusId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeId, 'status_id', 'id', TRUE);
613 if ($currentPledgeStatusId == array_search('Cancelled', $pledgeStatusTypes)) {
614 return $currentPledgeStatusId;
2c0f8c67
JP
615 }
616
cc28438b 617 // retrieve all pledge payments for this particular pledge
6a488035
TO
618 $allPledgePayments = $allStatus = array();
619 $returnProperties = array('status_id');
620 CRM_Core_DAO::commonRetrieveAll('CRM_Pledge_DAO_PledgePayment', 'pledge_id', $pledgeId, $allPledgePayments, $returnProperties);
621
622 // build pledge payment statuses
623 foreach ($allPledgePayments as $key => $value) {
624 $allStatus[$value['id']] = $paymentStatusTypes[$value['status_id']];
625 }
626
627 if (array_search('Overdue', $allStatus)) {
01dac399 628 $statusId = array_search('Overdue', $pledgeStatusTypes);
6a488035
TO
629 }
630 elseif (array_search('Completed', $allStatus)) {
631 if (count(array_count_values($allStatus)) == 1) {
01dac399 632 $statusId = array_search('Completed', $pledgeStatusTypes);
6a488035
TO
633 }
634 else {
01dac399 635 $statusId = array_search('In Progress', $pledgeStatusTypes);
6a488035
TO
636 }
637 }
638 else {
01dac399 639 $statusId = array_search('Pending', $pledgeStatusTypes);
6a488035
TO
640 }
641
642 return $statusId;
643 }
644
645 /**
fe482240 646 * Update pledge payment table.
6a488035 647 *
3a1617b6
TO
648 * @param int $pledgeId
649 * Pledge id.
650 * @param int $paymentStatusId
651 * Payment status id to set.
652 * @param array $paymentIds
653 * Payment ids to be updated.
77b97be7 654 * @param float|int $actualAmount , actual amount being paid
3a1617b6
TO
655 * @param int $contributionId
656 * , Id of associated contribution when payment is recorded.
657 * @param bool $isScriptUpdate
658 * , is function being called from bin script?.
77b97be7 659 *
6a488035 660 */
317fceb4 661 public static function updatePledgePayments(
3295515a 662 $pledgeId,
353ffa53
TO
663 $paymentStatusId,
664 $paymentIds = NULL,
665 $actualAmount = 0,
666 $contributionId = NULL,
667 $isScriptUpdate = FALSE
6a488035
TO
668 ) {
669 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
670 $paymentClause = NULL;
671 if (!empty($paymentIds)) {
672 $payments = implode(',', $paymentIds);
673 $paymentClause = " AND civicrm_pledge_payment.id IN ( {$payments} )";
674 }
762dc4cf
JP
675 elseif ($paymentStatusId == array_search('Cancelled', $allStatus)) {
676 $completedStatus = array_search('Completed', $allStatus);
677 $paymentClause = " AND civicrm_pledge_payment.status_id != {$completedStatus}";
678 }
6a488035
TO
679 $actualAmountClause = NULL;
680 $contributionIdClause = NULL;
681 if (isset($contributionId) && !$isScriptUpdate) {
682 $contributionIdClause = ", civicrm_pledge_payment.contribution_id = {$contributionId}";
683 $actualAmountClause = ", civicrm_pledge_payment.actual_amount = {$actualAmount}";
684 }
685
686 $query = "
687UPDATE civicrm_pledge_payment
688SET civicrm_pledge_payment.status_id = {$paymentStatusId}
689 {$actualAmountClause} {$contributionIdClause}
690WHERE civicrm_pledge_payment.pledge_id = %1
691 {$paymentClause}
692";
693
cc28438b 694 // get all status
6a488035
TO
695 $params = array(1 => array($pledgeId, 'Integer'));
696
697 $dao = CRM_Core_DAO::executeQuery($query, $params);
698 }
699
700 /**
fe482240 701 * Update pledge payment table when reminder is sent.
6a488035 702 *
3a1617b6
TO
703 * @param int $paymentId
704 * Payment id.
6a488035 705 */
00be9182 706 public static function updateReminderDetails($paymentId) {
6a488035
TO
707 $query = "
708UPDATE civicrm_pledge_payment
709SET civicrm_pledge_payment.reminder_date = CURRENT_TIMESTAMP,
710 civicrm_pledge_payment.reminder_count = civicrm_pledge_payment.reminder_count + 1
711WHERE civicrm_pledge_payment.id = {$paymentId}
712";
713 $dao = CRM_Core_DAO::executeQuery($query);
714 }
715
716 /**
fe482240 717 * Get oldest pending or in progress pledge payments.
6a488035 718 *
3a1617b6
TO
719 * @param int $pledgeID
720 * Pledge id.
6a488035 721 *
2a6da8d7
EM
722 * @param int $limit
723 *
a6c01b45
CW
724 * @return array
725 * associated array of pledge details
6a488035 726 */
00be9182 727 public static function getOldestPledgePayment($pledgeID, $limit = 1) {
cc28438b 728 // get pending / overdue statuses
01dac399
JP
729 $pledgeStatuses = CRM_Core_OptionGroup::values('pledge_status',
730 FALSE, FALSE, FALSE, NULL, 'name'
731 );
6a488035 732
cc28438b 733 // get pending and overdue payments
6a488035
TO
734 $status[] = array_search('Pending', $pledgeStatuses);
735 $status[] = array_search('Overdue', $pledgeStatuses);
736
737 $statusClause = " IN (" . implode(',', $status) . ")";
738
739 $query = "
5542b7c2 740SELECT civicrm_pledge_payment.id id, civicrm_pledge_payment.scheduled_amount amount, civicrm_pledge_payment.currency, civicrm_pledge_payment.scheduled_date,civicrm_pledge.financial_type_id
6a488035
TO
741FROM civicrm_pledge, civicrm_pledge_payment
742WHERE civicrm_pledge.id = civicrm_pledge_payment.pledge_id
743 AND civicrm_pledge_payment.status_id {$statusClause}
744 AND civicrm_pledge.id = %1
745ORDER BY civicrm_pledge_payment.scheduled_date ASC
746LIMIT 0, %2
747";
748
64041b14 749 $params[1] = array($pledgeID, 'Integer');
750 $params[2] = array($limit, 'Integer');
751 $payment = CRM_Core_DAO::executeQuery($query, $params);
752 $count = 1;
6a488035
TO
753 $paymentDetails = array();
754 while ($payment->fetch()) {
755 $paymentDetails[] = array(
756 'id' => $payment->id,
757 'amount' => $payment->amount,
758 'currency' => $payment->currency,
9fa00ed1 759 'schedule_date' => $payment->scheduled_date,
5542b7c2 760 'financial_type_id' => $payment->financial_type_id,
6a488035
TO
761 'count' => $count,
762 );
763 $count++;
764 }
765 return end($paymentDetails);
766 }
767
ffd93213 768 /**
100fef9d 769 * @param int $pledgeID
ffd93213
EM
770 * @param $actualAmount
771 * @param $pledgeScheduledAmount
100fef9d
CW
772 * @param int $paymentContributionId
773 * @param int $pPaymentId
774 * @param int $paymentStatusID
ffd93213 775 */
00be9182 776 public static function adjustPledgePayment($pledgeID, $actualAmount, $pledgeScheduledAmount, $paymentContributionId = NULL, $pPaymentId = NULL, $paymentStatusID = NULL) {
6a488035 777 $allStatus = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
a9ae3b2c
DG
778 if ($paymentStatusID == array_search('Cancelled', $allStatus) || $paymentStatusID == array_search('Refunded', $allStatus)) {
779 $query = "
780SELECT civicrm_pledge_payment.id id
781FROM civicrm_pledge_payment
782WHERE civicrm_pledge_payment.contribution_id = {$paymentContributionId}
783";
784 $paymentsAffected = CRM_Core_DAO::executeQuery($query);
785 $paymentIDs = array();
786 while ($paymentsAffected->fetch()) {
787 $paymentIDs[] = $paymentsAffected->id;
788 }
2efcf0c2 789 // Reset the affected values by the amount paid more than the scheduled amount
64041b14 790 foreach ($paymentIDs as $key => $value) {
a9ae3b2c
DG
791 $payment = new CRM_Pledge_DAO_PledgePayment();
792 $payment->id = $value;
793 if ($payment->find(TRUE)) {
794 $payment->contribution_id = 'null';
795 $payment->status_id = array_search('Pending', $allStatus);
796 $payment->scheduled_date = NULL;
797 $payment->reminder_date = NULL;
798 $payment->scheduled_amount = $pledgeScheduledAmount;
799 $payment->actual_amount = 'null';
800 $payment->save();
801 }
802 }
2efcf0c2 803
cc28438b 804 // Cancel the initial paid amount
a9ae3b2c
DG
805 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', reset($paymentIDs), 'status_id', $paymentStatusID, 'id');
806 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', reset($paymentIDs), 'actual_amount', $actualAmount, 'id');
2efcf0c2 807
cc28438b 808 // Add new payment after the last payment for the pledge
a9ae3b2c
DG
809 $allPayments = self::getPledgePayments($pledgeID);
810 $lastPayment = array_pop($allPayments);
811
812 $pledgeFrequencyUnit = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_unit', 'id');
813 $pledgeFrequencyInterval = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_Pledge', $pledgeID, 'frequency_interval', 'id');
64041b14 814 $pledgeScheduledDate = $lastPayment['scheduled_date'];
a9ae3b2c
DG
815 $scheduled_date = CRM_Utils_Date::processDate($pledgeScheduledDate);
816 $date['year'] = (int) substr($scheduled_date, 0, 4);
817 $date['month'] = (int) substr($scheduled_date, 4, 2);
818 $date['day'] = (int) substr($scheduled_date, 6, 2);
819 $newDate = date('YmdHis', mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
820 $ScheduledDate = CRM_Utils_Date::format(CRM_Utils_Date::intervalAdd($pledgeFrequencyUnit, $pledgeFrequencyInterval, $newDate));
821 $pledgeParams = array(
822 'status_id' => array_search('Pending', $allStatus),
823 'pledge_id' => $pledgeID,
824 'scheduled_amount' => $pledgeScheduledAmount,
825 'scheduled_date' => $ScheduledDate,
826 );
827 $payment = self::add($pledgeParams);
828 }
829 else {
64041b14 830 $oldestPayment = self::getOldestPledgePayment($pledgeID);
831 if (!$paymentContributionId) {
832 // means we are editing payment scheduled payment, so get the second pending to update.
833 $oldestPayment = self::getOldestPledgePayment($pledgeID, 2);
834 if (($oldestPayment['count'] != 1) && ($oldestPayment['id'] == $pPaymentId)) {
835 $oldestPayment = self::getOldestPledgePayment($pledgeID);
836 }
6a488035 837 }
6a488035 838
64041b14 839 if ($oldestPayment) {
840 // not the last scheduled payment and the actual amount is less than the expected , add it to oldest pending.
841 if (($actualAmount != $pledgeScheduledAmount) && (($actualAmount < $pledgeScheduledAmount) || (($actualAmount - $pledgeScheduledAmount) < $oldestPayment['amount']))) {
842 $oldScheduledAmount = $oldestPayment['amount'];
843 $newScheduledAmount = $oldScheduledAmount + ($pledgeScheduledAmount - $actualAmount);
cc28438b 844 // store new amount in oldest pending payment record.
7ca3c666 845 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment',
846 $oldestPayment['id'],
847 'scheduled_amount',
848 $newScheduledAmount
849 );
850 if (CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', $oldestPayment['id'], 'contribution_id', 'id')) {
851 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment',
852 $oldestPayment['id'],
853 'contribution_id',
854 $paymentContributionId
855 );
856 }
6a488035 857 }
64041b14 858 elseif (($actualAmount > $pledgeScheduledAmount) && (($actualAmount - $pledgeScheduledAmount) >= $oldestPayment['amount'])) {
859 // 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
860 // set the actual amount of the next pending to '0', set contribution Id to current contribution Id and status as completed
861 $paymentId = array($oldestPayment['id']);
862 self::updatePledgePayments($pledgeID, array_search('Completed', $allStatus), $paymentId, 0, $paymentContributionId);
863 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $oldestPayment['id'], 'scheduled_amount', 0, 'id');
864 $oldestPayment = self::getOldestPledgePayment($pledgeID);
865 if (!$paymentContributionId) {
866 // means we are editing payment scheduled payment.
867 $oldestPaymentAmount = self::getOldestPledgePayment($pledgeID, 2);
868 }
6a488035 869 $newActualAmount = ($actualAmount - $pledgeScheduledAmount);
64041b14 870 $newPledgeScheduledAmount = $oldestPayment['amount'];
871 if (!$paymentContributionId) {
872 $newActualAmount = ($actualAmount - $pledgeScheduledAmount);
873 $newPledgeScheduledAmount = $oldestPaymentAmount['amount'];
874 // means we are editing payment scheduled payment, so update scheduled amount.
875 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment',
876 $oldestPaymentAmount['id'],
877 'scheduled_amount',
878 $newActualAmount
879 );
880 }
881 if ($newActualAmount > 0) {
882 self::adjustPledgePayment($pledgeID, $newActualAmount, $newPledgeScheduledAmount, $paymentContributionId);
883 }
6a488035
TO
884 }
885 }
886 }
887 }
96025800 888
ab6ba136 889 /**
890 * Override buildOptions to hack out some statuses.
891 *
892 * @todo instead of using & hacking the shared optionGroup contribution_status use a separate one.
893 *
894 * @param string $fieldName
895 * @param string $context
896 * @param array $props
897 *
898 * @return array|bool
899 */
900 public static function buildOptions($fieldName, $context = NULL, $props = array()) {
901 $result = parent::buildOptions($fieldName, $context, $props);
902 if ($fieldName == 'status_id') {
74edda99 903 $result = CRM_Pledge_BAO_Pledge::buildOptions($fieldName, $context, $props);
ab6ba136 904 $result = array_diff($result, array('Failed', 'In Progress'));
905 }
906 return $result;
907 }
908
6a488035 909}