Merge pull request #13602 from civicrm/5.11
[civicrm-core.git] / tests / phpunit / CRM / Event / BAO / ChangeFeeSelectionTest.php
1 <?php
2 /**
3 * Class CRM_Event_BAO_AdditionalPaymentTest
4 * @group headless
5 */
6 class CRM_Event_BAO_ChangeFeeSelectionTest extends CiviUnitTestCase {
7
8 protected $_priceSetID;
9 protected $_cheapFee = 80;
10 protected $_expensiveFee = 100;
11 protected $_veryExpensive = 120;
12 protected $expensiveFeeValueID;
13 protected $cheapFeeValueID;
14 protected $veryExpensiveFeeValueID;
15
16 /**
17 * @var int
18 */
19 protected $contributionID;
20
21 /**
22 * @var int
23 */
24 protected $participantID;
25
26 /**
27 * Price set field id.
28 *
29 * @var int
30 */
31 protected $priceSetFieldID;
32
33 /**
34 * @var int
35 */
36 private $_contactId;
37
38 /**
39 * @var int
40 */
41 private $_eventId;
42
43 /**
44 * @var array
45 */
46 private $_feeBlock;
47
48 /**
49 * Set up for test.
50 */
51 public function setUp() {
52 parent::setUp();
53 $this->_contactId = $this->individualCreate();
54 $event = $this->eventCreate(array('is_monetary' => 1));
55 $this->_eventId = $event['id'];
56 $this->_priceSetID = $this->priceSetCreate();
57 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $this->_eventId, $this->_priceSetID);
58 $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($this->_priceSetID, TRUE, FALSE);
59 $priceSet = CRM_Utils_Array::value($this->_priceSetID, $priceSet);
60 $this->_feeBlock = CRM_Utils_Array::value('fields', $priceSet);
61 }
62
63 /**
64 * Clean up after test.
65 */
66 public function tearDown() {
67 $this->eventDelete($this->_eventId);
68 $this->quickCleanUpFinancialEntities();
69 }
70
71 /**
72 * Create an event with a price set.
73 *
74 * @todo resolve this with parent function.
75 * @param string $type
76 *
77 * @return int
78 */
79 protected function priceSetCreate($type = 'Radio') {
80 $feeTotal = 55;
81 $minAmt = 0;
82 $paramsSet['title'] = 'Two Options' . substr(sha1(rand()), 0, 4);
83 $paramsSet['name'] = CRM_Utils_String::titleToVar('Two Options') . substr(sha1(rand()), 0, 4);
84 $paramsSet['is_active'] = FALSE;
85 $paramsSet['extends'] = 1;
86
87 $priceSet = CRM_Price_BAO_PriceSet::create($paramsSet);
88
89 if ($type == 'Text') {
90 $paramsField = array(
91 'label' => 'Text Price Field',
92 'name' => CRM_Utils_String::titleToVar('text_price_field'),
93 'html_type' => 'Text',
94 'option_label' => array('1' => 'Text Price Field'),
95 'option_name' => array('1' => CRM_Utils_String::titleToVar('text_price_field')),
96 'option_weight' => array('1' => 1),
97 'option_amount' => array('1' => 10),
98 'option_count' => array(1 => 1),
99 'is_display_amounts' => 1,
100 'weight' => 1,
101 'options_per_line' => 1,
102 'is_active' => array('1' => 1),
103 'price_set_id' => $priceSet->id,
104 'is_enter_qty' => 1,
105 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
106 );
107 }
108 else {
109 $paramsField = array(
110 'label' => 'Price Field',
111 'name' => CRM_Utils_String::titleToVar('Two Options'),
112 'html_type' => 'Radio',
113 //'price' => $feeTotal,
114 'option_label' => array('1' => 'Expensive Room', '2' => "Cheap Room", '3' => 'Very Expensive'),
115 'option_value' => array('1' => 'E', '2' => 'C', '3' => 'V'),
116 'option_name' => array('1' => 'Expensive', '2' => "Cheap", "3" => "Very Expensive"),
117 'option_weight' => array('1' => 1, '2' => 2, '3' => 3),
118 'option_amount' => array('1' => $this->_expensiveFee, '2' => $this->_cheapFee, '3' => $this->_veryExpensive),
119 'option_count' => array(1 => 1, 2 => 1, 3 => 1),
120 'is_display_amounts' => 1,
121 'weight' => 1,
122 'options_per_line' => 1,
123 'is_active' => array('1' => 1),
124 'price_set_id' => $priceSet->id,
125 'is_enter_qty' => 1,
126 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
127 );
128 }
129 $field = CRM_Price_BAO_PriceField::create($paramsField);
130 $values = $this->callAPISuccess('PriceFieldValue', 'get', [
131 'price_field_id' => $field->id,
132 'return' => ['id', 'label']
133 ]);
134 foreach ($values['values'] as $value) {
135 switch ($value['label']) {
136 case 'Expensive Room':
137 $this->expensiveFeeValueID = $value['id'];
138 break;
139
140 case 'Cheap Room':
141 $this->cheapFeeValueID = $value['id'];
142 break;
143
144 case 'Very Expensive':
145 $this->veryExpensiveFeeValueID = $value['id'];
146 break;
147 }
148 }
149
150 $this->priceSetFieldID = $field->id;
151 return $priceSet->id;
152 }
153
154 /**
155 * Get the total for the invoice.
156 *
157 * @param int $contributionId
158 * @return mixed
159 */
160 private function contributionInvoice($contributionId) {
161 $query = "
162 SELECT SUM(line_total) total
163 FROM civicrm_line_item
164 WHERE contribution_id = {$contributionId}";
165 $dao = CRM_Core_DAO::executeQuery($query);
166
167 $this->assertTrue($dao->fetch(), "Succeeded retrieving invoicetotal");
168 return $dao->total;
169 }
170
171 /**
172 * Get the total income from the participant record.
173 *
174 * @param int $participantId
175 *
176 * @return mixed
177 */
178 private function totalIncome($participantId) {
179 $query = "
180 SELECT SUM(fi.amount) total
181 FROM civicrm_financial_item fi
182 INNER JOIN civicrm_line_item li ON li.id = fi.entity_id AND fi.entity_table = 'civicrm_line_item'
183 WHERE li.entity_table = 'civicrm_participant' AND li.entity_id = ${participantId}
184 ";
185 $dao = CRM_Core_DAO::executeQuery($query);
186
187 $this->assertTrue($dao->fetch(), "Succeeded retrieving total Income");
188 return $dao->total;
189 }
190
191 /**
192 * Check the relevant entity balances.
193 *
194 * @param float $amount
195 */
196 private function balanceCheck($amount) {
197 $this->assertEquals($amount, $this->contributionInvoice($this->_contributionId), "Invoice must a total of $amount");
198 $this->assertEquals($amount, $this->totalIncome($this->_participantId), "The recorded income must be $amount ");
199 }
200
201 /**
202 * Prepare records for editing.
203 */
204 public function registerParticipantAndPay($actualPaidAmt = NULL) {
205 $params = array(
206 'send_receipt' => 1,
207 'is_test' => 0,
208 'is_pay_later' => 0,
209 'event_id' => $this->_eventId,
210 'register_date' => date('Y-m-d') . " 00:00:00",
211 'role_id' => 1,
212 'status_id' => 1,
213 'source' => 'Event_' . $this->_eventId,
214 'contact_id' => $this->_contactId,
215 //'fee_level' => CRM_Core_DAO::VALUE_SEPARATOR.'Expensive Room'.CRM_Core_DAO::VALUE_SEPARATOR,
216 );
217 $participant = $this->callAPISuccess('Participant', 'create', $params);
218 $this->_participantId = $participant['id'];
219
220 $actualPaidAmt = $actualPaidAmt ? $actualPaidAmt : $this->_expensiveFee;
221
222 $contributionParams = array(
223 'total_amount' => $actualPaidAmt,
224 'source' => 'Testset with information',
225 'currency' => 'USD',
226 'receipt_date' => date('Y-m-d') . " 00:00:00",
227 'contact_id' => $this->_contactId,
228 'financial_type_id' => 4,
229 'payment_instrument_id' => 4,
230 'contribution_status_id' => 1,
231 'receive_date' => date('Y-m-d') . " 00:00:00",
232 'skipLineItem' => 1,
233 'partial_payment_total' => $this->_expensiveFee,
234 'partial_amount_to_pay' => $actualPaidAmt,
235 );
236
237 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
238 $this->_contributionId = $contribution['id'];
239
240 $this->callAPISuccess('participant_payment', 'create', array(
241 'participant_id' => $this->_participantId,
242 'contribution_id' => $this->_contributionId,
243 ));
244
245 $priceSetParams['price_' . $this->priceSetFieldID] = $this->expensiveFeeValueID;
246
247 $lineItems = CRM_Price_BAO_LineItem::buildLineItemsForSubmittedPriceField($priceSetParams);
248 CRM_Price_BAO_PriceSet::processAmount($this->_feeBlock, $priceSetParams, $lineItems);
249 $lineItemVal[$this->_priceSetID] = $lineItems;
250 CRM_Price_BAO_LineItem::processPriceSet($participant['id'], $lineItemVal, $this->getContributionObject($contribution['id']), 'civicrm_participant');
251 $this->balanceCheck($this->_expensiveFee);
252 $this->assertEquals(($this->_expensiveFee - $actualPaidAmt), CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
253
254 }
255
256 public function testCRM19273() {
257 $this->registerParticipantAndPay();
258
259 $priceSetParams['price_' . $this->priceSetFieldID] = $this->cheapFeeValueID;
260 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
261 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem, $this->_expensiveFee);
262 $this->balanceCheck($this->_cheapFee);
263
264 $priceSetParams['price_' . $this->priceSetFieldID] = $this->expensiveFeeValueID;
265 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
266
267 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem, $this->_expensiveFee);
268
269 $this->balanceCheck($this->_expensiveFee);
270
271 $priceSetParams['price_' . $this->priceSetFieldID] = $this->veryExpensiveFeeValueID;
272 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
273 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem, $this->_expensiveFee);
274 $this->balanceCheck($this->_veryExpensive);
275 }
276
277 /**
278 * CRM-21245: Test that Contribution status doesn't changed to 'Pending Refund' from 'Partially Paid' if the partially paid amount is lower then newly selected fee amount
279 */
280 public function testCRM21245() {
281 $this->registerParticipantAndPay(50);
282 $partiallyPaidContribuitonStatus = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Partially paid');
283 $this->assertEquals($this->callAPISuccessGetValue('Contribution', array('id' => $this->_contributionId, 'return' => 'contribution_status_id')), $partiallyPaidContribuitonStatus);
284
285 $priceSetParams['price_' . $this->priceSetFieldID] = $this->veryExpensiveFeeValueID;
286 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
287 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
288 $this->assertEquals($this->callAPISuccessGetValue('Contribution', array('id' => $this->_contributionId, 'return' => 'contribution_status_id')), $partiallyPaidContribuitonStatus);
289 }
290
291 /**
292 * Test that proper financial items are recorded for cancelled line items
293 */
294 public function testCRM20611() {
295 $this->registerParticipantAndPay();
296 $actualPaidAmount = 100;
297 $priceSetParams['price_' . $this->priceSetFieldID] = $this->expensiveFeeValueID;
298 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->participantID, 'participant');
299 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
300 $this->balanceCheck($this->_expensiveFee);
301 $contributionBalance = ($this->_expensiveFee - $actualPaidAmount);
302 $this->assertEquals($contributionBalance, CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
303
304 $priceSetParams['price_' . $this->priceSetFieldID] = $this->cheapFeeValueID;
305 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->participantID, 'participant');
306 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
307 $this->balanceCheck($this->_cheapFee);
308 $contributionBalance = ($this->_cheapFee - $actualPaidAmount);
309 $this->assertEquals($contributionBalance, CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
310
311 //Complete the refund payment.
312 $submittedValues = array(
313 'total_amount' => 120,
314 'payment_instrument_id' => 3,
315 );
316 CRM_Contribute_BAO_Contribution::recordAdditionalPayment($this->_contributionId, $submittedValues, 'refund', $this->_participantId);
317 $contributionBalance += 120;
318 $this->assertEquals($contributionBalance, CRM_Contribute_BAO_Contribution::getContributionBalance($this->_contributionId));
319
320 // retrieve the cancelled line-item information
321 $cancelledLineItem = $this->callAPISuccessGetSingle('LineItem', array(
322 'entity_table' => 'civicrm_participant',
323 'entity_id' => $this->_participantId,
324 'qty' => 0,
325 ));
326 // retrieve the related financial lin-items
327 $financialItems = $this->callAPISuccess('FinancialItem', 'Get', array(
328 'entity_id' => $cancelledLineItem['id'],
329 'entity_table' => 'civicrm_line_item',
330 ));
331 $this->assertEquals($financialItems['count'], 2, 'Financial Items for Cancelled fee is not proper');
332
333 $contributionCompletedStatusID = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
334 $expectedAmount = 100.00;
335 foreach ($financialItems['values'] as $id => $financialItem) {
336 $this->assertEquals($expectedAmount, $financialItem['amount']);
337 $this->assertNotEmpty($financialItem['financial_account_id']);
338 $this->assertEquals($contributionCompletedStatusID, $financialItem['status_id']);
339 $expectedAmount = -$expectedAmount;
340 }
341 }
342
343 /**
344 * Test to ensure that correct financial records are entered on text price field fee change on event registration
345 */
346 public function testCRM21513() {
347 $this->_priceSetID = $this->priceSetCreate('Text');
348 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $this->_eventId, $this->_priceSetID);
349 $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($this->_priceSetID, TRUE, FALSE);
350 $priceSet = CRM_Utils_Array::value($this->_priceSetID, $priceSet);
351 $this->_feeBlock = CRM_Utils_Array::value('fields', $priceSet);
352
353 $params = array(
354 'send_receipt' => 1,
355 'is_test' => 0,
356 'is_pay_later' => 0,
357 'event_id' => $this->_eventId,
358 'register_date' => date('Y-m-d') . " 00:00:00",
359 'role_id' => 1,
360 'status_id' => 1,
361 'source' => 'Event_' . $this->_eventId,
362 'contact_id' => $this->_contactId,
363 );
364 $participant = $this->callAPISuccess('Participant', 'create', $params);
365 $this->_participantId = $participant['id'];
366 $contributionParams = array(
367 'total_amount' => 10,
368 'source' => 'Testset with information',
369 'currency' => 'USD',
370 'receipt_date' => date('Y-m-d') . " 00:00:00",
371 'contact_id' => $this->_contactId,
372 'financial_type_id' => 4,
373 'payment_instrument_id' => 4,
374 'contribution_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_DAO_Contribution', 'contribution_status_id', 'Pending'),
375 'receive_date' => date('Y-m-d') . " 00:00:00",
376 'skipLineItem' => 1,
377 );
378
379 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams);
380 $this->_contributionId = $contribution['id'];
381
382 $this->callAPISuccess('participant_payment', 'create', array(
383 'participant_id' => $this->_participantId,
384 'contribution_id' => $this->_contributionId,
385 ));
386
387 // CASE 1: Choose text price qty 1 (x$10 = $10 amount)
388 $priceSetParams['price_' . $this->priceSetFieldID] = 1;
389 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
390 CRM_Price_BAO_PriceSet::processAmount($this->_feeBlock, $priceSetParams, $lineItem);
391 $lineItemVal[$this->_priceSetID] = $lineItem;
392 CRM_Price_BAO_LineItem::processPriceSet($this->_participantId, $lineItemVal, $this->getContributionObject($contribution['id']), 'civicrm_participant');
393
394 // CASE 2: Choose text price qty 3 (x$10 = $30 amount)
395 $priceSetParams['price_' . $this->priceSetFieldID] = 3;
396 $lineItem = CRM_Price_BAO_LineItem::getLineItems($participant['id'], 'participant');
397 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $participant['id'], 'participant', $this->_contributionId, $this->_feeBlock, $lineItem, 0);
398
399 // CASE 3: Choose text price qty 2 (x$10 = $20 amount)
400 $priceSetParams['price_' . $this->priceSetFieldID] = 2;
401 $lineItem = CRM_Price_BAO_LineItem::getLineItems($participant['id'], 'participant');
402 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $participant['id'], 'participant', $this->_contributionId, $this->_feeBlock, $lineItem, 0);
403
404 $financialItems = $this->callAPISuccess('FinancialItem', 'Get', array(
405 'entity_table' => 'civicrm_line_item',
406 'entity_id' => array('IN' => array_keys($lineItem)),
407 'sequential' => 1,
408 ));
409
410 $unpaidStatus = CRM_Core_PseudoConstant::getKey('CRM_Financial_DAO_FinancialItem', 'status_id', 'Unpaid');
411 $expectedResults = array(
412 array(
413 'amount' => 10.00, // when qty 1 is used
414 'status_id' => $unpaidStatus,
415 'entity_table' => 'civicrm_line_item',
416 'entity_id' => 1,
417 ),
418 array(
419 'amount' => 20.00, // when qty 3 is used, add the surplus amount i.e. $30 - $10 = $20
420 'status_id' => $unpaidStatus,
421 'entity_table' => 'civicrm_line_item',
422 'entity_id' => 1,
423 ),
424 array(
425 'amount' => -10.00, // when qty 2 is used, add the surplus amount i.e. $20 - $30 = -$10
426 'status_id' => $unpaidStatus,
427 'entity_table' => 'civicrm_line_item',
428 'entity_id' => 1,
429 ),
430 );
431 // Check if 3 financial items were recorded
432 $this->assertEquals(count($expectedResults), $financialItems['count']);
433 foreach ($expectedResults as $key => $expectedResult) {
434 foreach ($expectedResult as $column => $value) {
435 $this->assertEquals($expectedResult[$column], $financialItems['values'][$key][$column]);
436 }
437 }
438
439 $this->balanceCheck(20);
440 }
441
442 /**
443 * CRM-17151: Test that Contribution status change to 'Completed' if balance is zero.
444 */
445 public function testCRM17151() {
446 $this->registerParticipantAndPay();
447
448 $contributionStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
449 $partiallyPaidStatusId = array_search('Partially paid', $contributionStatuses);
450 $pendingRefundStatusId = array_search('Pending refund', $contributionStatuses);
451 $completedStatusId = array_search('Completed', $contributionStatuses);
452 $this->assertDBCompareValue('CRM_Contribute_BAO_Contribution', $this->_contributionId, 'contribution_status_id', 'id', $completedStatusId, 'Payment t be completed');
453 $priceSetParams['price_' . $this->priceSetFieldID] = $this->cheapFeeValueID;
454 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
455 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
456 $this->assertDBCompareValue('CRM_Contribute_BAO_Contribution', $this->_contributionId, 'contribution_status_id', 'id', $pendingRefundStatusId, 'Contribution must be refunding');
457 $priceSetParams['price_' . $this->priceSetFieldID] = $this->expensiveFeeValueID;
458 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
459 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
460 $this->assertDBCompareValue('CRM_Contribute_BAO_Contribution', $this->_contributionId, 'contribution_status_id', 'id', $completedStatusId, 'Contribution must, after complete payment be in state completed');
461 $priceSetParams['price_' . $this->priceSetFieldID] = $this->veryExpensiveFeeValueID;
462 $lineItem = CRM_Price_BAO_LineItem::getLineItems($this->_participantId, 'participant');
463 CRM_Price_BAO_LineItem::changeFeeSelections($priceSetParams, $this->_participantId, 'participant', $this->_contributionId, $this->_feeBlock, $lineItem);
464 $this->assertDBCompareValue('CRM_Contribute_BAO_Contribution', $this->_contributionId, 'contribution_status_id', 'id', $partiallyPaidStatusId, 'Partial Paid');
465 }
466
467 }