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