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