Unit test for #20342
[civicrm-core.git] / tests / phpunit / CRM / Contribute / BAO / ContributionTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11 use Civi\Api4\Activity;
12 use Civi\Api4\PledgePayment;
13
14 /**
15 * Class CRM_Contribute_BAO_ContributionTest
16 * @group headless
17 */
18 class CRM_Contribute_BAO_ContributionTest extends CiviUnitTestCase {
19
20 use CRMTraits_Financial_FinancialACLTrait;
21 use CRMTraits_Financial_PriceSetTrait;
22
23 /**
24 * Clean up after tests.
25 */
26 public function tearDown(): void {
27 $this->quickCleanUpFinancialEntities();
28 parent::tearDown();
29 }
30
31 /**
32 * Test create method (create and update modes).
33 *
34 * @throws \CRM_Core_Exception
35 */
36 public function testCreate() {
37 $contactId = $this->individualCreate();
38
39 $params = [
40 'contact_id' => $contactId,
41 'currency' => 'USD',
42 'financial_type_id' => 1,
43 'contribution_status_id' => 1,
44 'payment_instrument_id' => 1,
45 'source' => 'STUDENT',
46 'receive_date' => '20080522000000',
47 'receipt_date' => '20080522000000',
48 'non_deductible_amount' => 0.00,
49 'total_amount' => 200.00,
50 'fee_amount' => 5,
51 'net_amount' => 195,
52 'trxn_id' => '22ereerwww444444',
53 'invoice_id' => '86ed39c9e9ee6ef6031621ce0eafe7eb81',
54 'thankyou_date' => '20080522',
55 'sequential' => TRUE,
56 ];
57
58 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
59
60 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transaction id creation.');
61 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
62
63 //update contribution amount
64 $params['id'] = $contribution['id'];
65 $params['fee_amount'] = 10;
66 $params['net_amount'] = 190;
67
68 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
69
70 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
71 $this->assertEquals($params['net_amount'], $contribution['net_amount'], 'Check for Amount updation.');
72 }
73
74 /**
75 * Create() method with custom data.
76 */
77 public function testCreateWithCustomData() {
78 $contactId = $this->individualCreate();
79
80 //create custom data
81 $customGroup = $this->customGroupCreate(['extends' => 'Contribution']);
82 $customGroupID = $customGroup['id'];
83 $customGroup = $customGroup['values'][$customGroupID];
84
85 $fields = [
86 'label' => 'testFld',
87 'data_type' => 'String',
88 'html_type' => 'Text',
89 'is_active' => 1,
90 'custom_group_id' => $customGroupID,
91 ];
92 $customField = CRM_Core_BAO_CustomField::create($fields);
93
94 $params = [
95 'contact_id' => $contactId,
96 'currency' => 'USD',
97 'financial_type_id' => 1,
98 'contribution_status_id' => 1,
99 'payment_instrument_id' => 1,
100 'source' => 'STUDENT',
101 'receive_date' => '20080522000000',
102 'receipt_date' => '20080522000000',
103 'id' => NULL,
104 'non_deductible_amount' => 0.00,
105 'total_amount' => 200.00,
106 'fee_amount' => 5,
107 'net_amount' => 195,
108 'trxn_id' => '22ereerwww322323',
109 'invoice_id' => '22ed39c9e9ee6ef6031621ce0eafe6da70',
110 'thankyou_date' => '20080522',
111 'skipCleanMoney' => TRUE,
112 ];
113
114 $params['custom'] = [
115 $customField->id => [
116 -1 => [
117 'value' => 'Test custom value',
118 'type' => 'String',
119 'custom_field_id' => $customField->id,
120 'custom_group_id' => $customGroupID,
121 'table_name' => $customGroup['table_name'],
122 'column_name' => $customField->column_name,
123 'file_id' => NULL,
124 ],
125 ],
126 ];
127
128 $contribution = CRM_Contribute_BAO_Contribution::create($params);
129
130 // Check that the custom field value is saved
131 $customValueParams = [
132 'entityID' => $contribution->id,
133 'custom_' . $customField->id => 1,
134 ];
135 $values = CRM_Core_BAO_CustomValueTable::getValues($customValueParams);
136 $this->assertEquals('Test custom value', $values['custom_' . $customField->id], 'Check the custom field value');
137
138 $this->assertEquals($params['trxn_id'], $contribution->trxn_id, 'Check for transcation id creation.');
139 $this->assertEquals($contactId, $contribution->contact_id, 'Check for contact id for Conribution.');
140 }
141
142 /**
143 * CRM-21026 Test ContributionCount after contribution created with disabled FT
144 */
145 public function testContributionCountDisabledFinancialType() {
146 $contactId = $this->individualCreate();
147 $financialType = [
148 'name' => 'grassvariety1' . substr(sha1(rand()), 0, 7),
149 'is_reserved' => 0,
150 'is_active' => 0,
151 ];
152 $finType = $this->callAPISuccess('financial_type', 'create', $financialType);
153 $params = [
154 'contact_id' => $contactId,
155 'currency' => 'USD',
156 'financial_type_id' => $finType['id'],
157 'contribution_status_id' => 1,
158 'payment_instrument_id' => 1,
159 'source' => 'STUDENT',
160 'receive_date' => '20080522000000',
161 'receipt_date' => '20080522000000',
162 'id' => NULL,
163 'non_deductible_amount' => 0.00,
164 'total_amount' => 200.00,
165 'fee_amount' => 5,
166 'net_amount' => 195,
167 'trxn_id' => '22ereerwww322323',
168 'invoice_id' => '22ed39c9e9ee6ef6031621ce0eafe6da70',
169 'thankyou_date' => '20080522',
170 ];
171 $this->callAPISuccess('Contribution', 'create', $params);
172 $this->callAPISuccess('financial_type', 'create', ['is_active' => 0, 'id' => $finType['id']]);
173 $contributionCount = CRM_Contribute_BAO_Contribution::contributionCount($contactId);
174 $this->assertEquals(1, $contributionCount);
175 }
176
177 /**
178 * DeleteContribution() method
179 */
180 public function testDeleteContribution() {
181 $contactId = $this->individualCreate();
182
183 $params = [
184 'contact_id' => $contactId,
185 'currency' => 'USD',
186 'financial_type_id' => 1,
187 'contribution_status_id' => 1,
188 'payment_instrument_id' => 1,
189 'source' => 'STUDENT',
190 'receive_date' => '20080522000000',
191 'receipt_date' => '20080522000000',
192 'id' => NULL,
193 'non_deductible_amount' => 0.00,
194 'total_amount' => 200.00,
195 'fee_amount' => 5,
196 'net_amount' => 195,
197 'trxn_id' => '33ereerwww322323',
198 'invoice_id' => '33ed39c9e9ee6ef6031621ce0eafe6da70',
199 'thankyou_date' => '20080522',
200 'sequential' => TRUE,
201 ];
202
203 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
204
205 CRM_Contribute_BAO_Contribution::deleteContribution($contribution['id']);
206
207 $this->assertDBNull('CRM_Contribute_DAO_Contribution', $contribution['trxn_id'],
208 'id', 'trxn_id', 'Database check for deleted Contribution.'
209 );
210 }
211
212 /**
213 * Create honor-contact method.
214 */
215 public function testCreateAndGetHonorContact() {
216 $firstName = 'John_' . substr(sha1(rand()), 0, 7);
217 $lastName = 'Smith_' . substr(sha1(rand()), 0, 7);
218 $email = "{$firstName}.{$lastName}@example.com";
219
220 //Get profile id of name honoree_individual used to create profileContact
221 $honoreeProfileId = NULL;
222 $ufGroupDAO = new CRM_Core_DAO_UFGroup();
223 $ufGroupDAO->name = 'honoree_individual';
224 if ($ufGroupDAO->find(TRUE)) {
225 $honoreeProfileId = $ufGroupDAO->id;
226 }
227
228 $params = [
229 'prefix_id' => 3,
230 'first_name' => $firstName,
231 'last_name' => $lastName,
232 'email-1' => $email,
233 ];
234 $softParam = ['soft_credit_type_id' => 1];
235
236 $null = [];
237 $honoreeContactId = CRM_Contact_BAO_Contact::createProfileContact($params, $null,
238 NULL, NULL, $honoreeProfileId
239 );
240
241 $this->assertDBCompareValue('CRM_Contact_DAO_Contact', $honoreeContactId, 'first_name', 'id', $firstName,
242 'Database check for created honor contact record.'
243 );
244 //create contribution on behalf of honary.
245
246 $contactId = $this->individualCreate(['first_name' => 'John', 'last_name' => 'Doe']);
247
248 $param = [
249 'contact_id' => $contactId,
250 'currency' => 'USD',
251 'financial_type_id' => 4,
252 'contribution_status_id' => 1,
253 'receive_date' => date('Ymd'),
254 'total_amount' => 66,
255 'sequential' => 1,
256 ];
257
258 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
259 $id = $contribution['id'];
260 $softParam['contact_id'] = $honoreeContactId;
261 $softParam['contribution_id'] = $id;
262 $softParam['currency'] = $contribution['currency'];
263 $softParam['amount'] = $contribution['total_amount'];
264
265 //Create Soft Contribution for honoree contact
266 CRM_Contribute_BAO_ContributionSoft::add($softParam);
267
268 $this->assertDBCompareValue('CRM_Contribute_DAO_ContributionSoft', $id, 'contact_id',
269 'contribution_id', $honoreeContactId, 'Check DB for honor contact of the contribution'
270 );
271 //get honorary information
272 $getHonorContact = CRM_Contribute_BAO_Contribution::getHonorContacts($honoreeContactId);
273 $this->assertEquals([
274 $id => [
275 'honor_type' => 'In Honor of',
276 'honorId' => $contactId,
277 'display_name' => 'Mr. John Doe II',
278 'type' => 'Event Fee',
279 'type_id' => '4',
280 'amount' => '$ 66.00',
281 'source' => NULL,
282 'receive_date' => date('Y-m-d 00:00:00'),
283 'contribution_status' => 'Completed',
284 ],
285 ], $getHonorContact);
286
287 $this->assertDBCompareValue('CRM_Contact_DAO_Contact', $honoreeContactId, 'first_name', 'id', $firstName,
288 'Database check for created honor contact record.'
289 );
290
291 //get annual contribution information
292 $annual = CRM_Contribute_BAO_Contribution::annual($contactId);
293
294 $currencySymbol = CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_Currency', CRM_Core_Config::singleton()->defaultCurrency, 'symbol', 'name');
295 $this->assertDBCompareValue('CRM_Contribute_DAO_Contribution', $id, 'total_amount',
296 'id', ltrim($annual[2], $currencySymbol), 'Check DB for total amount of the contribution'
297 );
298 }
299
300 /**
301 * Test that financial type data is not added to the annual query if acls not enabled.
302 */
303 public function testAnnualQueryWithFinancialACLsEnabled() {
304 $this->enableFinancialACLs();
305 $this->createLoggedInUserWithFinancialACL();
306 $permittedFinancialType = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'financial_type_id', 'Donation');
307 $sql = CRM_Contribute_BAO_Contribution::getAnnualQuery([1, 2, 3]);
308 $this->assertContains('SUM(total_amount) as amount,', $sql);
309 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
310 $this->assertContains('b.financial_type_id IN (' . $permittedFinancialType . ')', $sql);
311
312 // Run it to make sure it's not bad sql.
313 CRM_Core_DAO::executeQuery($sql);
314 $this->disableFinancialACLs();
315 }
316
317 /**
318 * Test the annual query returns a correct result when multiple line items are present.
319 */
320 public function testAnnualWithMultipleLineItems() {
321 $contactID = $this->createLoggedInUserWithFinancialACL();
322 $this->createContributionWithTwoLineItemsAgainstPriceSet([
323 'contact_id' => $contactID,
324 ]
325 );
326 $this->enableFinancialACLs();
327 $sql = CRM_Contribute_BAO_Contribution::getAnnualQuery([$contactID]);
328 $result = CRM_Core_DAO::executeQuery($sql);
329 $result->fetch();
330 $this->assertEquals(300, $result->amount);
331 $this->assertEquals(1, $result->count);
332 $this->disableFinancialACLs();
333 }
334
335 /**
336 * Test that financial type data is not added to the annual query if acls not enabled.
337 */
338 public function testAnnualQueryWithFinancialACLsDisabled() {
339 $sql = CRM_Contribute_BAO_Contribution::getAnnualQuery([1, 2, 3]);
340 $this->assertContains('SUM(total_amount) as amount,', $sql);
341 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
342 $this->assertNotContains('b.financial_type_id', $sql);
343 //$this->assertNotContains('line_item', $sql);
344 // Run it to make sure it's not bad sql.
345 CRM_Core_DAO::executeQuery($sql);
346 }
347
348 /**
349 * Test that financial type data is not added to the annual query if acls not enabled.
350 */
351 public function testAnnualQueryWithFinancialHook() {
352 $this->hookClass->setHook('civicrm_selectWhereClause', [$this, 'aclIdNoZero']);
353 $sql = CRM_Contribute_BAO_Contribution::getAnnualQuery([1, 2, 3]);
354 $this->assertContains('SUM(total_amount) as amount,', $sql);
355 $this->assertContains('WHERE b.contact_id IN (1,2,3)', $sql);
356 $this->assertContains('b.id NOT IN (0)', $sql);
357 $this->assertNotContains('b.financial_type_id', $sql);
358 CRM_Core_DAO::executeQuery($sql);
359 }
360
361 /**
362 * Add ACL denying values LIKE '0'.
363 *
364 * @param string $entity
365 * @param string $clauses
366 */
367 public function aclIdNoZero($entity, &$clauses) {
368 if ($entity != 'Contribution') {
369 return;
370 }
371 $clauses['id'] = "NOT IN (0)";
372 }
373
374 /**
375 * Display sort name during.
376 * Update multiple contributions
377 * sortName();
378 */
379 public function testsortName() {
380 $params = [
381 'first_name' => 'Shane',
382 'last_name' => 'Whatson',
383 'contact_type' => 'Individual',
384 ];
385
386 $contact = CRM_Contact_BAO_Contact::add($params);
387
388 //Now check $contact is object of contact DAO..
389 $this->assertInstanceOf('CRM_Contact_DAO_Contact', $contact, 'Check for created object');
390
391 $contactId = $contact->id;
392 $param = [
393 'contact_id' => $contactId,
394 'currency' => 'USD',
395 'financial_type_id' => 1,
396 'contribution_status_id' => 1,
397 'payment_instrument_id' => 1,
398 'source' => 'STUDENT',
399 'receive_date' => '20080522000000',
400 'receipt_date' => '20080522000000',
401 'id' => NULL,
402 'non_deductible_amount' => 0.00,
403 'total_amount' => 300.00,
404 'fee_amount' => 5,
405 'net_amount' => 295,
406 'trxn_id' => '22ereerwww323',
407 'invoice_id' => '22ed39c9e9ee621ce0eafe6da70',
408 'thankyou_date' => '20080522',
409 'sequential' => TRUE,
410 ];
411
412 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
413
414 $this->assertEquals($param['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
415 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
416
417 //display sort name during Update multiple contributions
418 $sortName = CRM_Contribute_BAO_Contribution::sortName($contribution['id']);
419
420 $this->assertEquals('Whatson, Shane', $sortName, 'Check for sort name.');
421 }
422
423 /**
424 * Add premium during online Contribution.
425 *
426 * AddPremium();
427 */
428 public function testAddPremium() {
429 $contactId = $this->individualCreate();
430
431 $params = [
432 'name' => 'TEST Premium',
433 'sku' => 111,
434 'imageOption' => 'noImage',
435 'MAX_FILE_SIZE' => 2097152,
436 'price' => 100.00,
437 'cost' => 90.00,
438 'min_contribution' => 100,
439 'is_active' => 1,
440 ];
441 $premium = CRM_Contribute_BAO_Product::create($params);
442
443 $this->assertEquals('TEST Premium', $premium->name, 'Check for premium name.');
444
445 $contributionParams = [
446 'contact_id' => $contactId,
447 'currency' => 'USD',
448 'financial_type_id' => 1,
449 'contribution_status_id' => 1,
450 'payment_instrument_id' => 1,
451 'source' => 'STUDENT',
452 'receive_date' => '20080522000000',
453 'receipt_date' => '20080522000000',
454 'id' => NULL,
455 'non_deductible_amount' => 0.00,
456 'total_amount' => 300.00,
457 'fee_amount' => 5,
458 'net_amount' => 295,
459 'trxn_id' => '33erdfrwvw434',
460 'invoice_id' => '98ed34f7u9hh672ce0eafe8fb92',
461 'thankyou_date' => '20080522',
462 'sequential' => TRUE,
463 ];
464 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams)['values'][0];
465
466 $this->assertEquals($contributionParams['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
467 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
468
469 //parameter for adding premium to contribution
470 $data = [
471 'product_id' => $premium->id,
472 'contribution_id' => $contribution['id'],
473 'product_option' => NULL,
474 'quantity' => 1,
475 ];
476 $contributionProduct = CRM_Contribute_BAO_Contribution::addPremium($data);
477 $this->assertEquals($contributionProduct->product_id, $premium->id, 'Check for Product id .');
478
479 //Delete Product
480 CRM_Contribute_BAO_Product::del($premium->id);
481 $this->assertDBNull('CRM_Contribute_DAO_Product', $premium->name,
482 'id', 'name', 'Database check for deleted Product.'
483 );
484 }
485
486 /**
487 * Check duplicate contribution id.
488 * during the contribution import
489 * checkDuplicateIds();
490 */
491 public function testcheckDuplicateIds() {
492 $contactId = $this->individualCreate();
493
494 $param = [
495 'contact_id' => $contactId,
496 'currency' => 'USD',
497 'financial_type_id' => 1,
498 'contribution_status_id' => 1,
499 'payment_instrument_id' => 1,
500 'source' => 'STUDENT',
501 'receive_date' => '20080522000000',
502 'receipt_date' => '20080522000000',
503 'id' => NULL,
504 'non_deductible_amount' => 0.00,
505 'total_amount' => 300.00,
506 'fee_amount' => 5,
507 'net_amount' => 295,
508 'trxn_id' => '76ereeswww835',
509 'invoice_id' => '93ed39a9e9hd621bs0eafe3da82',
510 'thankyou_date' => '20080522',
511 'sequential' => TRUE,
512 ];
513
514 $contribution = $this->callAPISuccess('Contribution', 'create', $param)['values'][0];
515
516 $this->assertEquals($param['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
517 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
518 $data = [
519 'id' => $contribution['id'],
520 'trxn_id' => $contribution['trxn_id'],
521 'invoice_id' => $contribution['invoice_id'],
522 ];
523 $contributionID = CRM_Contribute_BAO_Contribution::checkDuplicateIds($data);
524 $this->assertEquals($contributionID, $contribution['id'], 'Check for duplicate transcation id .');
525 }
526
527 /**
528 * Create() method (create and update modes).
529 */
530 public function testIsPaymentFlag() {
531 $contactId = $this->individualCreate();
532
533 $params = [
534 'contact_id' => $contactId,
535 'currency' => 'USD',
536 'financial_type_id' => 1,
537 'contribution_status_id' => 1,
538 'payment_instrument_id' => 1,
539 'source' => 'STUDENT',
540 'receive_date' => '20080522000000',
541 'receipt_date' => '20080522000000',
542 'non_deductible_amount' => 0.00,
543 'total_amount' => 200.00,
544 'fee_amount' => 5,
545 'net_amount' => 195,
546 'trxn_id' => '22ereerwww4444xx',
547 'invoice_id' => '86ed39c9e9ee6ef6541621ce0eafe7eb81',
548 'thankyou_date' => '20080522',
549 'sequential' => TRUE,
550 ];
551 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
552
553 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id creation.');
554 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
555
556 $trxnArray = [
557 'trxn_id' => $params['trxn_id'],
558 'is_payment' => 1,
559 ];
560 $defaults = [];
561 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
562 $this->assertEquals(1, $financialTrxn->N, 'Mismatch count for is payment flag.');
563 //update contribution amount
564 $params['id'] = $contribution['id'];
565 $params['total_amount'] = 150;
566 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
567
568 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
569 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for Amount updation.');
570 $trxnArray = [
571 'trxn_id' => $params['trxn_id'],
572 'is_payment' => 1,
573 ];
574 $defaults = [];
575 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
576 $this->assertEquals(2, $financialTrxn->N, 'Mismatch count for is payment flag.');
577 $trxnArray['is_payment'] = 0;
578 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
579 $this->assertEquals(1, $financialTrxn->N, 'Mismatch count for is payment flag.');
580 }
581
582 /**
583 * Create() method (create and update modes).
584 */
585 public function testIsPaymentFlagForPending() {
586 $contactId = $this->individualCreate();
587
588 $params = [
589 'contact_id' => $contactId,
590 'currency' => 'USD',
591 'financial_type_id' => 1,
592 'contribution_status_id' => 2,
593 'payment_instrument_id' => 1,
594 'source' => 'STUDENT',
595 'is_pay_later' => 1,
596 'receive_date' => '20080522000000',
597 'receipt_date' => '20080522000000',
598 'non_deductible_amount' => 0.00,
599 'total_amount' => 200.00,
600 'fee_amount' => 5,
601 'net_amount' => 195,
602 'trxn_id' => '22ereerwww4444yy',
603 'invoice_id' => '86ed39c9e9yy6ef6541621ce0eafe7eb81',
604 'thankyou_date' => '20080522',
605 'sequential' => TRUE,
606 ];
607
608 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
609
610 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transaction id creation.');
611 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
612
613 $trxnArray = [
614 'trxn_id' => $params['trxn_id'],
615 'is_payment' => 0,
616 ];
617 $defaults = [];
618 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
619 $this->assertEquals(2, $financialTrxn->N, 'Mismatch count for is payment flag.');
620 $trxnArray['is_payment'] = 1;
621 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
622 $this->assertEquals(NULL, $financialTrxn, 'Mismatch count for is payment flag.');
623 //update contribution amount
624 $params['id'] = $contribution['id'];
625 $params['contribution_status_id'] = 1;
626
627 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
628
629 $this->assertEquals($params['trxn_id'], $contribution['trxn_id'], 'Check for transcation id .');
630 $this->assertEquals($params['contribution_status_id'], $contribution['contribution_status_id'], 'Check for status updation.');
631 $trxnArray = [
632 'trxn_id' => $params['trxn_id'],
633 'is_payment' => 1,
634 ];
635 $defaults = [];
636 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
637 $this->assertEquals(1, $financialTrxn->N, 'Mismatch count for is payment flag.');
638 $trxnArray['is_payment'] = 0;
639 $financialTrxn = CRM_Core_BAO_FinancialTrxn::retrieve($trxnArray, $defaults);
640 $this->assertEquals(2, $financialTrxn->N, 'Mismatch count for is payment flag.');
641 }
642
643 /**
644 * checks db values for financial item
645 */
646 public function checkItemValues($contribution) {
647 $toFinancialAccount = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount(4, 'Accounts Receivable Account is');
648 $query = "SELECT eft1.entity_id, ft.total_amount, eft1.amount FROM civicrm_financial_trxn ft INNER JOIN civicrm_entity_financial_trxn eft ON (eft.financial_trxn_id = ft.id AND eft.entity_table = 'civicrm_contribution')
649 INNER JOIN civicrm_entity_financial_trxn eft1 ON (eft1.financial_trxn_id = eft.financial_trxn_id AND eft1.entity_table = 'civicrm_financial_item')
650 WHERE eft.entity_id = %1 AND ft.to_financial_account_id <> %2";
651
652 $queryParams[1] = [$contribution->id, 'Integer'];
653 $queryParams[2] = [$toFinancialAccount, 'Integer'];
654
655 $dao = CRM_Core_DAO::executeQuery($query, $queryParams);
656 $amounts = [100.00, 50.00];
657 while ($dao->fetch()) {
658 $this->assertEquals(150.00, $dao->total_amount, 'Mismatch of total amount paid.');
659 $this->assertEquals($dao->amount, array_pop($amounts), 'Mismatch of amount proportionally assigned to financial item');
660 }
661 }
662
663 /**
664 * assignProportionalLineItems() method (add and edit modes of participant)
665 *
666 * @throws \CRM_Core_Exception
667 * @throws \CiviCRM_API3_Exception
668 */
669 public function testAssignProportionalLineItems() {
670 $contribution = $this->addParticipantWithContribution();
671 // Delete existing financial_trxns. This is because we are testing a code flow we
672 // want to deprecate & remove & the test relies on bad data asa starting point.
673 // End goal is the Order.create->Payment.create flow.
674 CRM_Core_DAO::executeQuery('DELETE FROM civicrm_entity_financial_trxn WHERE entity_table = "civicrm_financial_item"');
675 $params = [
676 'contribution_id' => $contribution->id,
677 'total_amount' => 150.00,
678 ];
679 $trxn = new CRM_Financial_DAO_FinancialTrxn();
680 $trxn->orderBy('id DESC');
681 $trxn->find(TRUE);
682 CRM_Contribute_BAO_Contribution::assignProportionalLineItems($params, $trxn->id, $contribution->total_amount);
683 $this->checkItemValues($contribution);
684 }
685
686 /**
687 * Add participant with contribution
688 *
689 * @return CRM_Contribute_BAO_Contribution
690 *
691 * @throws \CRM_Core_Exception
692 * @throws \CiviCRM_API3_Exception
693 */
694 public function addParticipantWithContribution() {
695 // creating price set, price field
696 $this->_contactId = $this->individualCreate();
697 $event = $this->eventCreatePaid([]);
698 $this->_eventId = $event['id'];
699 $priceSetID = $this->ids['PriceSet']['event'];
700 $paramsField = [
701 'label' => 'Price Field',
702 'name' => CRM_Utils_String::titleToVar('Price Field'),
703 'html_type' => 'CheckBox',
704 'option_label' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
705 'option_value' => ['1' => 100, '2' => 200],
706 'option_name' => ['1' => 'Price Field 1', '2' => 'Price Field 2'],
707 'option_weight' => ['1' => 1, '2' => 2],
708 'option_amount' => ['1' => 100, '2' => 200],
709 'is_display_amounts' => 1,
710 'weight' => 1,
711 'options_per_line' => 1,
712 'is_active' => ['1' => 1, '2' => 1],
713 'price_set_id' => $priceSetID,
714 'is_enter_qty' => 1,
715 'financial_type_id' => CRM_Core_DAO::getFieldValue('CRM_Financial_DAO_FinancialType', 'Event Fee', 'id', 'name'),
716 ];
717 $priceField = CRM_Price_BAO_PriceField::create($paramsField);
718 $eventParams = [
719 'id' => $this->_eventId,
720 'financial_type_id' => 4,
721 'is_monetary' => 1,
722 ];
723 CRM_Event_BAO_Event::create($eventParams);
724 CRM_Price_BAO_PriceSet::addTo('civicrm_event', $this->_eventId, $priceSetID);
725
726 $priceFields = $this->callAPISuccess('PriceFieldValue', 'get', ['price_field_id' => $priceField->id]);
727 $participantParams = [
728 'financial_type_id' => 4,
729 'event_id' => $this->_eventId,
730 'role_id' => 1,
731 'status_id' => 14,
732 'fee_currency' => 'USD',
733 'contact_id' => $this->_contactId,
734 ];
735 $participant = CRM_Event_BAO_Participant::add($participantParams);
736 $contributionParams = [
737 'total_amount' => 300,
738 'currency' => 'USD',
739 'contact_id' => $this->_contactId,
740 'financial_type_id' => 4,
741 'contribution_status_id' => 'Pending',
742 'contribution_mode' => 'participant',
743 'participant_id' => $participant->id,
744 'sequential' => TRUE,
745 'api.Payment.create' => ['total_amount' => 150],
746 ];
747
748 foreach ($priceFields['values'] as $key => $priceField) {
749 $lineItems[1][$key] = [
750 'price_field_id' => $priceField['price_field_id'],
751 'price_field_value_id' => $priceField['id'],
752 'label' => $priceField['label'],
753 'field_title' => $priceField['label'],
754 'qty' => 1,
755 'unit_price' => $priceField['amount'],
756 'line_total' => $priceField['amount'],
757 'financial_type_id' => $priceField['financial_type_id'],
758 ];
759 }
760 $contributionParams['line_item'] = $lineItems;
761 $contribution = $this->callAPISuccess('Contribution', 'create', $contributionParams)['values'][0];
762
763 $paymentParticipant = [
764 'participant_id' => $participant->id,
765 'contribution_id' => $contribution['id'],
766 ];
767 CRM_Event_BAO_ParticipantPayment::create($paymentParticipant);
768
769 $contributionObject = new CRM_Contribute_BAO_Contribution();
770 $contributionObject->id = $contribution['id'];
771 $contributionObject->find(TRUE);
772
773 return $contributionObject;
774 }
775
776 /**
777 * checkLineItems() check if total amount matches the sum of line total
778 */
779 public function testcheckLineItems() {
780 $params = [
781 'contact_id' => 202,
782 'receive_date' => '2010-01-20',
783 'total_amount' => 100,
784 'financial_type_id' => 3,
785 'line_items' => [
786 [
787 'line_item' => [
788 [
789 'entity_table' => 'civicrm_contribution',
790 'price_field_id' => 8,
791 'price_field_value_id' => 16,
792 'label' => 'test 1',
793 'qty' => 1,
794 'unit_price' => 100,
795 'line_total' => 100,
796 ],
797 [
798 'entity_table' => 'civicrm_contribution',
799 'price_field_id' => 8,
800 'price_field_value_id' => 17,
801 'label' => 'Test 2',
802 'qty' => 1,
803 'unit_price' => 200,
804 'line_total' => 200,
805 'financial_type_id' => 1,
806 ],
807 ],
808 'params' => [],
809 ],
810 ],
811 ];
812
813 try {
814 CRM_Contribute_BAO_Contribution::checkLineItems($params);
815 $this->fail("Missed expected exception");
816 }
817 catch (CRM_Contribute_Exception_CheckLineItemsException $e) {
818 $this->assertEquals(
819 CRM_Contribute_Exception_CheckLineItemsException::LINE_ITEM_DIFFERRING_TOTAL_EXCEPTON_MSG,
820 $e->getMessage()
821 );
822 }
823
824 $this->assertEquals(3, $params['line_items'][0]['line_item'][0]['financial_type_id']);
825 $params['total_amount'] = 300;
826
827 CRM_Contribute_BAO_Contribution::checkLineItems($params);
828 }
829
830 /**
831 * Tests CRM_Contribute_BAO_Contribution::checkLineItems() method works with
832 * floating point values.
833 */
834 public function testCheckLineItemsWithFloatingPointValues() {
835 $params = [
836 'contact_id' => 202,
837 'receive_date' => date('Y-m-d'),
838 'total_amount' => 16.67,
839 'financial_type_id' => 3,
840 'line_items' => [
841 [
842 'line_item' => [
843 [
844 'entity_table' => 'civicrm_contribution',
845 'price_field_id' => 8,
846 'price_field_value_id' => 16,
847 'label' => 'test 1',
848 'qty' => 1,
849 'unit_price' => 14.85,
850 'line_total' => 14.85,
851 ],
852 [
853 'entity_table' => 'civicrm_contribution',
854 'price_field_id' => 8,
855 'price_field_value_id' => 17,
856 'label' => 'Test 2',
857 'qty' => 1,
858 'unit_price' => 1.66,
859 'line_total' => 1.66,
860 'financial_type_id' => 1,
861 ],
862 [
863 'entity_table' => 'civicrm_contribution',
864 'price_field_id' => 8,
865 'price_field_value_id' => 17,
866 'label' => 'Test 2',
867 'qty' => 1,
868 'unit_price' => 0.16,
869 'line_total' => 0.16,
870 'financial_type_id' => 1,
871 ],
872 ],
873 'params' => [],
874 ],
875 ],
876 ];
877
878 $foundException = FALSE;
879
880 try {
881 CRM_Contribute_BAO_Contribution::checkLineItems($params);
882 }
883 catch (CRM_Contribute_Exception_CheckLineItemsException $e) {
884 $foundException = TRUE;
885 }
886
887 $this->assertFalse($foundException);
888 }
889
890 /**
891 * Test activity amount updates activity subject.
892 */
893 public function testActivityCreate() {
894 $contactId = $this->individualCreate();
895
896 $params = [
897 'contact_id' => $contactId,
898 'currency' => 'USD',
899 'financial_type_id' => 1,
900 'contribution_status_id' => 1,
901 'payment_instrument_id' => 1,
902 'source' => 'STUDENT',
903 'receive_date' => '20080522000000',
904 'receipt_date' => '20080522000000',
905 'non_deductible_amount' => 0.00,
906 'total_amount' => 100.00,
907 'invoice_id' => '86ed39c9e9ee6ef6031621ce0eafe7eb81',
908 'thankyou_date' => '20160519',
909 'sequential' => 1,
910 ];
911
912 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
913
914 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for total amount in contribution.');
915 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
916 $activityWhere = [
917 ['source_record_id', '=', $contribution['id']],
918 ['activity_type_id:name', '=', 'Contribution'],
919 ];
920 $activity = Activity::get()->setWhere($activityWhere)->setSelect(['source_record_id', 'subject'])->execute()->first();
921
922 $this->assertEquals($contribution['id'], $activity['source_record_id'], 'Check for activity associated with contribution.');
923 $this->assertEquals('$ 100.00 - STUDENT', $activity['subject'], 'Check for total amount in activity.');
924
925 $params['id'] = $contribution['id'];
926 $params['total_amount'] = 200;
927 $params['campaign_id'] = $this->campaignCreate();
928
929 $contribution = $this->callAPISuccess('Contribution', 'create', $params)['values'][0];
930
931 $this->assertEquals($params['total_amount'], $contribution['total_amount'], 'Check for total amount in contribution.');
932 $this->assertEquals($contactId, $contribution['contact_id'], 'Check for contact id creation.');
933
934 // Retrieve activity again.
935 $activity = Activity::get()->setWhere($activityWhere)->setSelect(['source_record_id', 'subject', 'campaign_id'])->execute()->first();
936
937 $this->assertEquals($contribution['id'], $activity['source_record_id'], 'Check for activity associated with contribution.');
938 $this->assertEquals('$ 200.00 - STUDENT', $activity['subject'], 'Check for total amount in activity.');
939 $this->assertEquals($params['campaign_id'], $activity['campaign_id']);
940 }
941
942 /**
943 * Test allowUpdateRevenueRecognitionDate.
944 *
945 * @throws \CRM_Core_Exception
946 */
947 public function testAllowUpdateRevenueRecognitionDate() {
948 $contactId = $this->individualCreate();
949 $params = [
950 'contact_id' => $contactId,
951 'receive_date' => '2010-01-20',
952 'total_amount' => 100,
953 'financial_type_id' => 4,
954 'contribution_status_id' => 'Pending',
955 ];
956 $order = $this->callAPISuccess('Order', 'create', $params);
957 $allowUpdate = CRM_Contribute_BAO_Contribution::allowUpdateRevenueRecognitionDate($order['id']);
958 $this->assertTrue($allowUpdate);
959
960 $event = $this->eventCreate();
961 $params = [
962 'contact_id' => $contactId,
963 'receive_date' => '2010-01-20',
964 'total_amount' => 300,
965 'financial_type_id' => $this->getFinancialTypeId('Event Fee'),
966 'contribution_status_id' => 'Pending',
967 ];
968 $priceFields = $this->createPriceSet('event', $event['id']);
969 foreach ($priceFields['values'] as $key => $priceField) {
970 $lineItems[$key] = [
971 'price_field_id' => $priceField['price_field_id'],
972 'price_field_value_id' => $priceField['id'],
973 'label' => $priceField['label'],
974 'field_title' => $priceField['label'],
975 'qty' => 1,
976 'unit_price' => $priceField['amount'],
977 'line_total' => $priceField['amount'],
978 'financial_type_id' => $priceField['financial_type_id'],
979 'entity_table' => 'civicrm_participant',
980 ];
981 }
982 $params['line_items'][] = [
983 'line_item' => $lineItems,
984 'params' => [
985 'contact_id' => $contactId,
986 'event_id' => $event['id'],
987 'status_id' => 1,
988 'role_id' => 1,
989 'register_date' => '2007-07-21 00:00:00',
990 'source' => 'Online Event Registration: API Testing',
991 ],
992 ];
993 $order = $this->callAPISuccess('Order', 'create', $params);
994 $allowUpdate = CRM_Contribute_BAO_Contribution::allowUpdateRevenueRecognitionDate($order['id']);
995 $this->assertFalse($allowUpdate);
996
997 $params = [
998 'contact_id' => $contactId,
999 'receive_date' => '2010-01-20',
1000 'total_amount' => 200,
1001 'financial_type_id' => $this->getFinancialTypeId('Member Dues'),
1002 'contribution_status_id' => 'Pending',
1003 ];
1004 $membershipType = $this->membershipTypeCreate();
1005 $priceFields = $this->createPriceSet();
1006 $lineItems = [];
1007 foreach ($priceFields['values'] as $key => $priceField) {
1008 $lineItems[$key] = [
1009 'price_field_id' => $priceField['price_field_id'],
1010 'price_field_value_id' => $priceField['id'],
1011 'label' => $priceField['label'],
1012 'field_title' => $priceField['label'],
1013 'qty' => 1,
1014 'unit_price' => $priceField['amount'],
1015 'line_total' => $priceField['amount'],
1016 'financial_type_id' => $priceField['financial_type_id'],
1017 'entity_table' => 'civicrm_membership',
1018 'membership_type_id' => $membershipType,
1019 ];
1020 }
1021 $params['line_items'][] = [
1022 'line_item' => [array_pop($lineItems)],
1023 'params' => [
1024 'contact_id' => $contactId,
1025 'membership_type_id' => $membershipType,
1026 'join_date' => '2006-01-21',
1027 'start_date' => '2006-01-21',
1028 'end_date' => '2006-12-21',
1029 'source' => 'Payment',
1030 'is_override' => 1,
1031 'status_id' => 1,
1032 ],
1033 ];
1034 $order = $this->callAPISuccess('Order', 'create', $params);
1035 $allowUpdate = CRM_Contribute_BAO_Contribution::allowUpdateRevenueRecognitionDate($order['id']);
1036 $this->assertFalse($allowUpdate);
1037 }
1038
1039 /**
1040 * Test calculateFinancialItemAmount().
1041 */
1042 public function testcalculateFinancialItemAmount() {
1043 $testParams = [
1044 [
1045 'params' => [],
1046 'amountParams' => [
1047 'line_total' => 100,
1048 'previous_line_total' => 300,
1049 'diff' => 1,
1050 ],
1051 'context' => 'changedAmount',
1052 'expectedItemAmount' => -200,
1053 ],
1054 [
1055 'params' => [],
1056 'amountParams' => [
1057 'line_total' => 100,
1058 'previous_line_total' => 100,
1059 'diff' => -1,
1060 ],
1061 // Most contexts are ignored. Removing refs to change payment instrument so placeholder.
1062 'context' => 'not null',
1063 'expectedItemAmount' => -100,
1064 ],
1065 [
1066 'params' => [
1067 'is_quick_config' => TRUE,
1068 'total_amount' => 110,
1069 'tax_amount' => 10,
1070 ],
1071 'amountParams' => [
1072 'item_amount' => 100,
1073 ],
1074 'context' => 'changedAmount',
1075 'expectedItemAmount' => 100,
1076 ],
1077 [
1078 'params' => [
1079 'is_quick_config' => TRUE,
1080 'total_amount' => 110,
1081 'tax_amount' => 10,
1082 ],
1083 'amountParams' => [
1084 'item_amount' => NULL,
1085 ],
1086 'context' => 'changedAmount',
1087 'expectedItemAmount' => 110,
1088 ],
1089 [
1090 'params' => [
1091 'is_quick_config' => TRUE,
1092 'total_amount' => 110,
1093 'tax_amount' => 10,
1094 ],
1095 'amountParams' => [
1096 'item_amount' => NULL,
1097 ],
1098 'context' => NULL,
1099 'expectedItemAmount' => 100,
1100 ],
1101 ];
1102 foreach ($testParams as $params) {
1103 $itemAmount = CRM_Contribute_BAO_Contribution::calculateFinancialItemAmount($params['params'], $params['amountParams'], $params['context']);
1104 $this->assertEquals($itemAmount, $params['expectedItemAmount'], 'Invalid Financial Item amount.');
1105 }
1106 }
1107
1108 /**
1109 * Test recording of amount with comma separator.
1110 *
1111 * @throws \CRM_Core_Exception
1112 */
1113 public function testCommaSeparatorAmount() {
1114 $contactId = $this->individualCreate();
1115
1116 $params = [
1117 'contact_id' => $contactId,
1118 'currency' => 'USD',
1119 'financial_type_id' => 1,
1120 'contribution_status_id' => 'Pending',
1121 'payment_instrument_id' => 1,
1122 'receive_date' => '20080522000000',
1123 'receipt_date' => '20080522000000',
1124 'total_amount' => '20,000.00',
1125 'api.Payment.create' => ['total_amount' => '8,000.00'],
1126 'skipCleanMoney' => FALSE,
1127 ];
1128
1129 $contribution = $this->callAPISuccess('Order', 'create', $params);
1130 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
1131 $financialTrxn = $this->callAPISuccessGetSingle(
1132 'FinancialTrxn',
1133 [
1134 'id' => $lastFinancialTrxnId['financialTrxnId'],
1135 'return' => ['total_amount'],
1136 ]
1137 );
1138 $this->assertEquals($financialTrxn['total_amount'], 8000, 'Invalid amount.');
1139 }
1140
1141 /**
1142 * Test for function getSalesTaxFinancialAccounts().
1143 */
1144 public function testgetSalesTaxFinancialAccounts() {
1145 $this->enableTaxAndInvoicing();
1146 $financialType = $this->createFinancialType();
1147 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1148 $expectedResult = [$financialAccount->financial_account_id => $financialAccount->financial_account_id];
1149 $financialType = $this->createFinancialType();
1150 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1151 $expectedResult[$financialAccount->financial_account_id] = $financialAccount->financial_account_id;
1152 $salesTaxFinancialAccount = CRM_Contribute_BAO_Contribution::getSalesTaxFinancialAccounts();
1153 $this->assertTrue(($salesTaxFinancialAccount == $expectedResult), 'Function returned wrong values.');
1154 }
1155
1156 /**
1157 * Test for function createProportionalEntry().
1158 *
1159 * @param string $thousandSeparator
1160 * punctuation used to refer to thousands.
1161 *
1162 * @dataProvider getThousandSeparators
1163 */
1164 public function testCreateProportionalEntry($thousandSeparator) {
1165 $this->setCurrencySeparators($thousandSeparator);
1166 [$contribution, $financialAccount] = $this->createContributionWithTax();
1167 $params = [
1168 'total_amount' => 55,
1169 'to_financial_account_id' => $financialAccount->financial_account_id,
1170 'payment_instrument_id' => 1,
1171 'trxn_date' => date('Ymd'),
1172 'status_id' => 1,
1173 'entity_id' => $contribution['id'],
1174 ];
1175 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'create', $params);
1176 $entityParams = [
1177 'contribution_total_amount' => $contribution['total_amount'],
1178 'trxn_total_amount' => 55,
1179 'line_item_amount' => 100,
1180 ];
1181 $previousLineItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($contribution['id']);
1182 $eftParams = [
1183 'entity_table' => 'civicrm_financial_item',
1184 'entity_id' => $previousLineItem['id'],
1185 'financial_trxn_id' => (string) $financialTrxn['id'],
1186 ];
1187 CRM_Contribute_BAO_Contribution::createProportionalEntry($entityParams, $eftParams);
1188 $trxnTestArray = array_merge($eftParams, [
1189 'amount' => '50.00',
1190 ]);
1191 $this->callAPISuccessGetSingle('EntityFinancialTrxn', $eftParams, $trxnTestArray);
1192 }
1193
1194 /**
1195 * Test for function createProportionalEntry with zero amount().
1196 *
1197 * @param string $thousandSeparator
1198 * punctuation used to refer to thousands.
1199 *
1200 * @throws \CRM_Core_Exception
1201 * @throws \CiviCRM_API3_Exception
1202 * @dataProvider getThousandSeparators
1203 */
1204 public function testCreateProportionalEntryZeroAmount(string $thousandSeparator): void {
1205 $this->setCurrencySeparators($thousandSeparator);
1206 [$contribution, $financialAccount] = $this->createContributionWithTax(['total_amount' => 0]);
1207 $params = [
1208 'total_amount' => 0,
1209 'to_financial_account_id' => $financialAccount->financial_account_id,
1210 'payment_instrument_id' => 1,
1211 'trxn_date' => date('Ymd'),
1212 'status_id' => 1,
1213 'entity_id' => $contribution['id'],
1214 ];
1215 $financialTrxn = $this->callAPISuccess('FinancialTrxn', 'create', $params);
1216 $entityParams = [
1217 'contribution_total_amount' => $contribution['total_amount'],
1218 'trxn_total_amount' => 0,
1219 'line_item_amount' => 0,
1220 ];
1221 $previousLineItem = CRM_Financial_BAO_FinancialItem::getPreviousFinancialItem($contribution['id']);
1222 $eftParams = [
1223 'entity_table' => 'civicrm_financial_item',
1224 'entity_id' => $previousLineItem['id'],
1225 'financial_trxn_id' => (string) $financialTrxn['id'],
1226 ];
1227 CRM_Contribute_BAO_Contribution::createProportionalEntry($entityParams, $eftParams);
1228 $trxnTestArray = array_merge($eftParams, [
1229 'amount' => '0.00',
1230 ]);
1231 $this->callAPISuccessGetSingle('EntityFinancialTrxn', $eftParams, $trxnTestArray);
1232 }
1233
1234 /**
1235 * Test for function getLastFinancialItemIds().
1236 */
1237 public function testgetLastFinancialItemIds() {
1238 [$contribution, $financialAccount] = $this->createContributionWithTax();
1239 [$ftIds, $taxItems] = CRM_Contribute_BAO_Contribution::getLastFinancialItemIds($contribution['id']);
1240 $this->assertEquals(count($ftIds), 1, 'Invalid count.');
1241 $this->assertEquals(count($taxItems), 1, 'Invalid count.');
1242 foreach ($taxItems as $value) {
1243 $this->assertEquals($value['amount'], 10, 'Invalid tax amount.');
1244 }
1245 }
1246
1247 /**
1248 * Test to ensure proportional entries are creating when adding a payment..
1249 *
1250 * In this test we create a pending contribution for $110 consisting of $100 contribution and $10 tax.
1251 *
1252 * We pay $50, resulting in it being allocated as $45.45 paymnt & $4.55 tax. This is in equivalent proportions
1253 * to the original payment - ie. .0909 of the $110 is 10 & that * 50 is $4.54 (note the rounding seems wrong as it should be
1254 * saved un-rounded).
1255 */
1256 public function testCreateProportionalFinancialEntriesViaPaymentCreate() {
1257 [$contribution, $financialAccount] = $this->createContributionWithTax([], FALSE);
1258 $params = [
1259 'total_amount' => 50,
1260 'to_financial_account_id' => $financialAccount->financial_account_id,
1261 'payment_instrument_id' => 1,
1262 'trxn_date' => date('Ymd'),
1263 'status_id' => 1,
1264 'entity_id' => $contribution['id'],
1265 'contribution_id' => $contribution['id'],
1266 ];
1267 $financialTrxn = $this->callAPISuccess('Payment', 'create', $params);
1268 $eftParams = [
1269 'entity_table' => 'civicrm_financial_item',
1270 'financial_trxn_id' => $financialTrxn['id'],
1271 ];
1272 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'Get', $eftParams);
1273 $this->assertEquals($entityFinancialTrxn['count'], 2, 'Invalid count.');
1274 $testAmount = [4.55, 45.45];
1275 foreach ($entityFinancialTrxn['values'] as $value) {
1276 $this->assertEquals(array_pop($testAmount), $value['amount'], 'Invalid amount stored in civicrm_entity_financial_trxn.');
1277 }
1278 }
1279
1280 /**
1281 * Test to check if amount is proportionally asigned for PI change.
1282 */
1283 public function testProportionallyAssignedForPIChange() {
1284 [$contribution, $financialAccount] = $this->createContributionWithTax();
1285 $params = [
1286 'id' => $contribution['id'],
1287 'payment_instrument_id' => 3,
1288 ];
1289 $this->callAPISuccess('Contribution', 'create', $params);
1290 $lastFinancialTrxnId = CRM_Core_BAO_FinancialTrxn::getFinancialTrxnId($contribution['id'], 'DESC');
1291 $eftParams = [
1292 'entity_table' => 'civicrm_financial_item',
1293 'financial_trxn_id' => $lastFinancialTrxnId['financialTrxnId'],
1294 ];
1295 $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'Get', $eftParams);
1296 $this->assertEquals($entityFinancialTrxn['count'], 2, 'Invalid count.');
1297 $testAmount = [10, 100];
1298 foreach ($entityFinancialTrxn['values'] as $value) {
1299 $this->assertEquals($value['amount'], array_pop($testAmount), 'Invalid amount stored in civicrm_entity_financial_trxn.');
1300 }
1301 }
1302
1303 /**
1304 * Function to create contribution with tax.
1305 */
1306 public function createContributionWithTax($params = [], $isCompleted = TRUE) {
1307 if (!isset($params['total_amount'])) {
1308 $params['total_amount'] = 100;
1309 }
1310 $contactId = $this->individualCreate();
1311 $this->enableTaxAndInvoicing();
1312 $financialType = $this->createFinancialType();
1313 $financialAccount = $this->addTaxAccountToFinancialType($financialType['id']);
1314 $form = new CRM_Contribute_Form_Contribution();
1315
1316 $form->testSubmit([
1317 'total_amount' => $params['total_amount'],
1318 'financial_type_id' => $financialType['id'],
1319 'contact_id' => $contactId,
1320 'contribution_status_id' => $isCompleted ? 1 : 2,
1321 'price_set_id' => 0,
1322 ], CRM_Core_Action::ADD);
1323 $contribution = $this->callAPISuccessGetSingle('Contribution',
1324 [
1325 'contact_id' => $contactId,
1326 'return' => ['tax_amount', 'total_amount'],
1327 ]
1328 );
1329 return [$contribution, $financialAccount];
1330 }
1331
1332 /**
1333 * Test processOnBehalfOrganization() function.
1334 */
1335 public function testProcessOnBehalfOrganization() {
1336 $orgInfo = [
1337 'phone' => '11111111',
1338 'email' => 'testorg@gmail.com',
1339 'street_address' => 'test Street',
1340 'city' => 'test City',
1341 'state_province' => 'AA',
1342 'postal_code' => '222222',
1343 'country' => 'United States',
1344 ];
1345 $originalContactId = $contactID = $this->individualCreate();
1346 $orgId = $this->organizationCreate(['organization_name' => 'testorg1']);
1347 $orgCount = $this->callAPISuccessGetCount('Contact', [
1348 'contact_type' => "Organization",
1349 'organization_name' => "testorg1",
1350 ]);
1351 $this->assertEquals($orgCount, 1);
1352
1353 $values = $params = [];
1354 $originalBehalfOrganization = $behalfOrganization = [
1355 'organization_name' => 'testorg1',
1356 'phone' => [
1357 1 => [
1358 'phone' => $orgInfo['phone'],
1359 'is_primary' => 1,
1360 ],
1361 ],
1362 'email' => [
1363 1 => [
1364 'email' => $orgInfo['email'],
1365 'is_primary' => 1,
1366 ],
1367 ],
1368 'address' => [
1369 3 => [
1370 'street_address' => $orgInfo['street_address'],
1371 'city' => $orgInfo['city'],
1372 'location_type_id' => 3,
1373 'postal_code' => $orgInfo['postal_code'],
1374 'country' => 'US',
1375 'state_province' => 'AA',
1376 'is_primary' => 1,
1377 ],
1378 ],
1379 ];
1380 $fields = [
1381 'organization_name' => 1,
1382 'phone-3-1' => 1,
1383 'email-3' => 1,
1384 'street_address-3' => 1,
1385 'city-3' => 1,
1386 'postal_code-3' => 1,
1387 'country-3' => 1,
1388 'state_province-3' => 1,
1389 ];
1390 $empty = [];
1391 CRM_Contribute_Form_Contribution_Confirm::processOnBehalfOrganization($behalfOrganization, $contactID, $empty, $empty, $empty);
1392
1393 //Check whether new organisation is created.
1394 $result = $this->callAPISuccess('Contact', 'get', [
1395 'contact_type' => "Organization",
1396 'organization_name' => "testorg1",
1397 ]);
1398 $this->assertEquals($result['count'], 1);
1399
1400 //Assert all org values are updated.
1401 foreach ($orgInfo as $key => $val) {
1402 $this->assertEquals($result['values'][$orgId][$key], $val);
1403 }
1404
1405 //Check if alert is assigned to params if more than 1 dupe exists.
1406 $orgId = $this->organizationCreate(['organization_name' => 'testorg1', 'email' => 'testorg@gmail.com']);
1407 CRM_Contribute_Form_Contribution_Confirm::processOnBehalfOrganization($originalBehalfOrganization, $originalContactId, $values, $params, $fields);
1408 $this->assertEquals($params['onbehalf_dupe_alert'], 1);
1409 }
1410
1411 /**
1412 * Test for replaceContributionTokens.
1413 * This function tests whether the contribution tokens are replaced with values from contribution.
1414 */
1415 public function testReplaceContributionTokens() {
1416 $customGroup = $this->customGroupCreate(['extends' => 'Contribution', 'title' => 'contribution stuff']);
1417 $customField = $this->customFieldOptionValueCreate($customGroup, 'myCustomField');
1418 $contactId1 = $this->individualCreate();
1419 $params = [
1420 'contact_id' => $contactId1,
1421 'receive_date' => '20120511',
1422 'total_amount' => 100.00,
1423 'financial_type_id' => 1,
1424 'trxn_id' => 12345,
1425 'invoice_id' => 67890,
1426 'source' => 'SSF',
1427 'contribution_status_id' => 2,
1428 "custom_{$customField['id']}" => 'value1',
1429 'currency' => 'EUR',
1430 ];
1431 $contribution1 = $this->contributionCreate($params);
1432 $contactId2 = $this->individualCreate();
1433 $params = [
1434 'contact_id' => $contactId2,
1435 'receive_date' => '20150511',
1436 'total_amount' => 200.00,
1437 'financial_type_id' => 1,
1438 'trxn_id' => 6789,
1439 'invoice_id' => 12345,
1440 'source' => 'ABC',
1441 'contribution_status_id' => 1,
1442 "custom_{$customField['id']}" => 'value2',
1443 ];
1444 $contribution2 = $this->contributionCreate($params);
1445 $ids = [$contribution1, $contribution2];
1446
1447 $subject = "This is a test for contribution ID: {contribution.contribution_id}";
1448 $text = "Contribution Amount: {contribution.total_amount}";
1449 $html = "<p>Contribution Source: {contribution.contribution_source}</p></br>
1450 <p>Contribution Invoice ID: {contribution.invoice_id}</p></br>
1451 <p>Contribution Receive Date: {contribution.receive_date}</p></br>
1452 <p>Contribution Custom Field: {contribution.custom_{$customField['id']}}</p></br>";
1453
1454 $subjectToken = CRM_Utils_Token::getTokens($subject);
1455 $messageToken = CRM_Utils_Token::getTokens($text);
1456 $messageToken = array_merge($messageToken, CRM_Utils_Token::getTokens($html));
1457
1458 $contributionDetails = CRM_Contribute_BAO_Contribution::replaceContributionTokens(
1459 $ids,
1460 $subject,
1461 $subjectToken,
1462 $text,
1463 $html,
1464 $messageToken,
1465 TRUE
1466 );
1467
1468 $this->assertEquals("Contribution Amount: € 100.00", $contributionDetails[$contactId1]['text'], "The text does not match");
1469 $this->assertEquals("<p>Contribution Source: ABC</p></br>
1470 <p>Contribution Invoice ID: 12345</p></br>
1471 <p>Contribution Receive Date: May 11th, 2015 12:00 AM</p></br>
1472 <p>Contribution Custom Field: Label2</p></br>", $contributionDetails[$contactId2]['html'], "The html does not match");
1473 }
1474
1475 /**
1476 * Test for contribution with deferred revenue.
1477 */
1478 public function testContributionWithDeferredRevenue() {
1479 $contactId = $this->individualCreate();
1480 Civi::settings()->set('deferred_revenue_enabled', TRUE);
1481 $params = [
1482 'contact_id' => $contactId,
1483 'receive_date' => '20120511',
1484 'total_amount' => 100.00,
1485 'financial_type_id' => 'Event Fee',
1486 'trxn_id' => 12345,
1487 'invoice_id' => 67890,
1488 'source' => 'SSF',
1489 'contribution_status_id' => 'Completed',
1490 'revenue_recognition_date' => date('Ymd', strtotime("+3 month")),
1491 ];
1492 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1493
1494 $this->callAPISuccessGetCount('EntityFinancialTrxn', [
1495 'entity_table' => "civicrm_contribution",
1496 'entity_id' => $contribution['id'],
1497 ], 2);
1498
1499 $checkAgainst = [
1500 'financial_trxn_id.to_financial_account_id.name' => 'Deferred Revenue - Event Fee',
1501 'financial_trxn_id.from_financial_account_id.name' => 'Event Fee',
1502 'financial_trxn_id' => '2',
1503 ];
1504 $result = $this->callAPISuccessGetSingle('EntityFinancialTrxn', [
1505 'return' => [
1506 "financial_trxn_id.from_financial_account_id.name",
1507 "financial_trxn_id.to_financial_account_id.name",
1508 "financial_trxn_id",
1509 ],
1510 'entity_table' => "civicrm_contribution",
1511 'entity_id' => $contribution['id'],
1512 'financial_trxn_id.is_payment' => 0,
1513 ], $checkAgainst);
1514
1515 $result = $this->callAPISuccessGetSingle('EntityFinancialTrxn', [
1516 'entity_table' => "civicrm_financial_item",
1517 'financial_trxn_id' => $result['financial_trxn_id'],
1518 'return' => ['entity_id'],
1519 ]);
1520
1521 $checkAgainst = [
1522 'financial_account_id.name' => 'Deferred Revenue - Event Fee',
1523 'id' => $result['entity_id'],
1524 ];
1525 $result = $this->callAPISuccessGetSingle('FinancialItem', [
1526 'id' => $result['entity_id'],
1527 'return' => ["financial_account_id.name"],
1528 ], $checkAgainst);
1529 }
1530
1531 /**
1532 * https://lab.civicrm.org/dev/financial/issues/56
1533 * Changing financial type on a contribution records correct financial items
1534 */
1535 public function testChangingFinancialTypeWithoutTax() {
1536 $ids = $values = [];
1537 $contactId = $this->individualCreate();
1538 $params = [
1539 'contact_id' => $contactId,
1540 'receive_date' => date('YmdHis'),
1541 'total_amount' => 100.00,
1542 'financial_type_id' => 'Donation',
1543 'contribution_status_id' => 'Completed',
1544 ];
1545 /* first test the scenario when sending an email */
1546 $contributionId = $this->callAPISuccess(
1547 'contribution',
1548 'create',
1549 $params
1550 )['id'];
1551
1552 // Update Financial Type.
1553 $this->callAPISuccess('contribution', 'create', [
1554 'id' => $contributionId,
1555 'financial_type_id' => 'Event Fee',
1556 ]);
1557
1558 // Get line item
1559 $lineItem = $this->callAPISuccessGetSingle('LineItem', [
1560 'contribution_id' => $contributionId,
1561 'return' => ["financial_type_id.name", "line_total"],
1562 ]);
1563
1564 $this->assertEquals(
1565 $lineItem['line_total'],
1566 100.00,
1567 'Invalid line amount.'
1568 );
1569
1570 $this->assertEquals(
1571 $lineItem['financial_type_id.name'],
1572 'Event Fee',
1573 'Invalid Financial Type stored.'
1574 );
1575
1576 // Get Financial Items.
1577 $financialItems = $this->callAPISuccess('FinancialItem', 'get', [
1578 'entity_id' => $lineItem['id'],
1579 'sequential' => 1,
1580 'entity_table' => 'civicrm_line_item',
1581 'options' => ['sort' => "id"],
1582 'return' => ["financial_account_id.name", "amount", "description"],
1583 ]);
1584
1585 $this->assertEquals($financialItems['count'], 3, 'Count mismatch.');
1586
1587 $toCheck = [
1588 ['Donation', 100.00],
1589 ['Donation', -100.00],
1590 ['Event Fee', 100.00],
1591 ];
1592
1593 foreach ($financialItems['values'] as $key => $values) {
1594 $this->assertEquals(
1595 $values['financial_account_id.name'],
1596 $toCheck[$key][0],
1597 'Invalid Financial Account stored.'
1598 );
1599 $this->assertEquals(
1600 $values['amount'],
1601 $toCheck[$key][1],
1602 'Amount mismatch.'
1603 );
1604 $this->assertEquals(
1605 $values['description'],
1606 'Contribution Amount',
1607 'Description mismatch.'
1608 );
1609 }
1610
1611 // Check transactions.
1612 $financialTransactions = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
1613 'return' => ["financial_trxn_id"],
1614 'entity_table' => "civicrm_contribution",
1615 'entity_id' => $contributionId,
1616 'sequential' => 1,
1617 ]);
1618 $this->assertEquals($financialTransactions['count'], 3, 'Count mismatch.');
1619
1620 foreach ($financialTransactions['values'] as $key => $values) {
1621 $this->callAPISuccessGetCount('EntityFinancialTrxn', [
1622 'financial_trxn_id' => $values['financial_trxn_id'],
1623 'amount' => $toCheck[$key][1],
1624 'financial_trxn_id.total_amount' => $toCheck[$key][1],
1625 ], 2);
1626 }
1627 }
1628
1629 /**
1630 * CRM-21424 Check if the receipt update is set after composing the receipt message
1631 */
1632 public function testSendMailUpdateReceiptDate() {
1633 $ids = $values = [];
1634 $contactId = $this->individualCreate();
1635 $params = [
1636 'contact_id' => $contactId,
1637 'receive_date' => '20120511',
1638 'total_amount' => 100.00,
1639 'financial_type_id' => 'Donation',
1640 'source' => 'SSF',
1641 'contribution_status_id' => 'Completed',
1642 ];
1643 /* first test the scenario when sending an email */
1644 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1645 $contributionId = $contribution['id'];
1646 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After creating receipt date must be null');
1647 $input = ['receipt_update' => 0];
1648 CRM_Contribute_BAO_Contribution::sendMail($input, $ids, $contributionId, $values);
1649 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail, with the explicit instruction not to update receipt date stays null');
1650 $input = ['receipt_update' => 1];
1651 CRM_Contribute_BAO_Contribution::sendMail($input, $ids, $contributionId, $values);
1652 $this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set');
1653
1654 /* repeat the same scenario for downloading a pdf */
1655 $contribution = $this->callAPISuccess('contribution', 'create', $params);
1656 $contributionId = $contribution['id'];
1657 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After creating receipt date must be null');
1658 $input = ['receipt_update' => 0];
1659 /* setting the lasast parameter (returnmessagetext) to TRUE is done by the download of the pdf */
1660 CRM_Contribute_BAO_Contribution::sendMail($input, $ids, $contributionId, $values, TRUE);
1661 $this->assertDBNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail, with the explicit instruction not to update receipt date stays null');
1662 $input = ['receipt_update' => 1];
1663 CRM_Contribute_BAO_Contribution::sendMail($input, $ids, $contributionId, $values, TRUE);
1664 $this->assertDBNotNull('CRM_Contribute_BAO_Contribution', $contributionId, 'receipt_date', 'id', 'After sendMail with the permission to allow update receipt date must be set');
1665 }
1666
1667 /**
1668 * Test cancel order api when a pledge is linked.
1669 *
1670 * The pledge status should be updated. I believe the contribution should
1671 * also be unlinked but the goal at this point is no change.
1672 *
1673 * @throws CRM_Core_Exception
1674 * @throws \CiviCRM_API3_Exception
1675 * @throws \API_Exception
1676 */
1677 public function testCancelOrderWithPledge(): void {
1678 $this->ids['contact'][0] = $this->individualCreate();
1679 $pledgeID = (int) $this->callAPISuccess('Pledge', 'create', ['contact_id' => $this->ids['contact'][0], 'amount' => 4, 'installments' => 2, 'frequency_unit' => 'month', 'original_installment_amount' => 2, 'create_date' => 'now', 'financial_type_id' => 'Donation', 'start_date' => '+5 days'])['id'];
1680 $orderID = (int) $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['contact'][0], 'total_amount' => 2, 'financial_type_id' => 'Donation', 'api.Payment.create' => ['total_amount' => 2]])['id'];
1681 $pledgePayments = $this->callAPISuccess('PledgePayment', 'get')['values'];
1682 $this->callAPISuccess('PledgePayment', 'create', ['id' => key($pledgePayments), 'pledge_id' => $pledgeID, 'contribution_id' => $orderID, 'status_id' => 'Completed', 'actual_amount' => 2]);
1683 $beforePledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
1684 $this->assertEquals(2, $beforePledge['pledge_total_paid']);
1685 $this->callAPISuccess('Order', 'cancel', ['contribution_id' => $orderID]);
1686
1687 $this->callAPISuccessGetSingle('Contribution', ['contribution_status_id' => 'Cancelled']);
1688 $afterPledge = $this->callAPISuccessGetSingle('Pledge', ['id' => $pledgeID]);
1689 $this->assertEquals('', $afterPledge['pledge_total_paid']);
1690 $payments = PledgePayment::get(FALSE)->addWhere('contribution_id', 'IS NOT NULL')->execute();
1691 $this->assertCount(0, $payments);
1692 }
1693
1694 /**
1695 * Test contribution update when more than one quick
1696 * config line item is linked to contribution.
1697 *
1698 * @throws CRM_Core_Exception
1699 * @throws \CiviCRM_API3_Exception
1700 * @throws \API_Exception
1701 */
1702 public function testContributionQuickConfigTwoLineItems(): void {
1703 $contactId1 = $this->individualCreate();
1704 $contactId2 = $this->individualCreate();
1705 $membershipOrganizationId = $this->organizationCreate();
1706
1707 // Created new contribution to bypass the deprecated error
1708 // 'Per https://lab.civicrm.org/dev/core/issues/15 this data fix should not be required.'
1709 // in CRM_Price_BAO_LineItem::processPriceSet();
1710 $this->callAPISuccess('Contribution', 'create', [
1711 'contact_id' => $contactId1,
1712 'receive_date' => '2010-01-20',
1713 'financial_type_id' => 'Member Dues',
1714 'contribution_status_id' => 'Completed',
1715 'total_amount' => 150,
1716 ]);
1717 $this->callAPISuccess('Contribution', 'create', [
1718 'contact_id' => $contactId1,
1719 'receive_date' => '2010-01-20',
1720 'financial_type_id' => 'Member Dues',
1721 'contribution_status_id' => 'Completed',
1722 'total_amount' => 150,
1723 ]);
1724
1725 // create membership type
1726 $membershipTypeId1 = $this->callAPISuccess('MembershipType', 'create', [
1727 'domain_id' => 1,
1728 'member_of_contact_id' => $membershipOrganizationId,
1729 'financial_type_id' => 'Member Dues',
1730 'duration_unit' => 'month',
1731 'duration_interval' => 1,
1732 'period_type' => 'rolling',
1733 'minimum_fee' => 100,
1734 'name' => 'Parent',
1735 ])['id'];
1736
1737 $membershipTypeId2 = $this->callAPISuccess('MembershipType', 'create', [
1738 'domain_id' => 1,
1739 'member_of_contact_id' => $membershipOrganizationId,
1740 'financial_type_id' => 'Member Dues',
1741 'duration_unit' => 'month',
1742 'duration_interval' => 1,
1743 'period_type' => 'rolling',
1744 'minimum_fee' => 50,
1745 'name' => 'Child',
1746 ])['id'];
1747
1748 $contactIds = [
1749 $contactId1 => $membershipTypeId1,
1750 $contactId2 => $membershipTypeId2,
1751 ];
1752
1753 $priceFields = CRM_Price_BAO_PriceSet::getDefaultPriceSet('membership');
1754
1755 // prepare order api params.
1756 $p = [
1757 'contact_id' => $contactId1,
1758 'receive_date' => '2010-01-20',
1759 'financial_type_id' => 'Member Dues',
1760 'contribution_status_id' => 'Pending',
1761 'total_amount' => 150,
1762 'api.Payment.create' => ['total_amount' => 150],
1763 ];
1764
1765 $now = date('Ymd');
1766 foreach ($priceFields as $priceField) {
1767 $lineItems = [];
1768 $contactId = array_search($priceField['membership_type_id'], $contactIds);
1769 $lineItems[1] = [
1770 'price_field_id' => $priceField['priceFieldID'],
1771 'price_field_value_id' => $priceField['priceFieldValueID'],
1772 'label' => $priceField['label'],
1773 'field_title' => $priceField['label'],
1774 'qty' => 1,
1775 'unit_price' => $priceField['amount'],
1776 'line_total' => $priceField['amount'],
1777 'financial_type_id' => $priceField['financial_type_id'],
1778 'entity_table' => 'civicrm_membership',
1779 'membership_type_id' => $priceField['membership_type_id'],
1780 ];
1781 $p['line_items'][] = [
1782 'line_item' => $lineItems,
1783 'params' => [
1784 'contact_id' => $contactId,
1785 'membership_type_id' => $priceField['membership_type_id'],
1786 'source' => 'Payment',
1787 'join_date' => '2020-04-28',
1788 'start_date' => '2020-04-28',
1789 'status_id' => 'Pending',
1790 'is_override' => 1,
1791 ],
1792 ];
1793 }
1794 $order = $this->callAPISuccess('order', 'create', $p);
1795 $contributionId = $order['id'];
1796
1797 $count = CRM_Core_DAO::singleValueQuery('
1798 SELECT count(*), total_amount
1799 FROM civicrm_contribution cc
1800 INNER JOIN civicrm_line_item cli
1801 ON cli.contribution_id = cc.id
1802 AND cc.id = %1
1803 GROUP BY cc.id, total_amount
1804 HAVING SUM(cli.line_total) != total_amount
1805 ', [1 => [$contributionId, 'Integer']]);
1806
1807 $this->assertEquals(0, $count);
1808
1809 $this->callAPISuccess('Contribution', 'create', [
1810 'id' => $contributionId,
1811 'total_amount' => 150,
1812 ]);
1813 $count = CRM_Core_DAO::singleValueQuery('
1814 SELECT count(*), total_amount
1815 FROM civicrm_contribution cc
1816 INNER JOIN civicrm_line_item cli
1817 ON cli.contribution_id = cc.id
1818 AND cc.id = %1
1819 GROUP BY cc.id, total_amount
1820 HAVING SUM(cli.line_total) != total_amount
1821 ', [1 => [$contributionId, 'Integer']]);
1822
1823 $this->assertEquals(0, $count);
1824 }
1825
1826 /**
1827 * Test activity contact is updated when contribution contact is changed
1828 */
1829 public function testUpdateActivityContactOnContributionContactChange(): void {
1830 $contactId_1 = $this->individualCreate();
1831 $contactId_2 = $this->individualCreate();
1832 $contactId_3 = $this->individualCreate();
1833
1834 $contributionParams = [
1835 'financial_type_id' => 'Donation',
1836 'receive_date' => date('Y-m-d H:i:s'),
1837 'sequential' => TRUE,
1838 'total_amount' => 50,
1839 ];
1840
1841 // Case 1: Only source contact, no target contact
1842
1843 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1844 $contributionParams,
1845 ['contact_id' => $contactId_1]
1846 ))['values'][0];
1847
1848 $activity = $this->callAPISuccessGetSingle('Activity', ['source_record_id' => $contribution['id']]);
1849
1850 $activityContactParams = [
1851 'activity_id' => $activity['id'],
1852 'record_type_id' => 'Activity Source',
1853 ];
1854
1855 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1856
1857 $this->assertEquals($activityContact['contact_id'], $contactId_1, 'Check source contact ID matches the first contact');
1858
1859 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1860 $contributionParams,
1861 [
1862 'id' => $contribution['id'],
1863 'contact_id' => $contactId_2,
1864 ]
1865 ))['values'][0];
1866
1867 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1868
1869 $this->assertEquals($activityContact['contact_id'], $contactId_2, 'Check source contact ID matches the second contact');
1870
1871 // Case 2: Source and target contact
1872
1873 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1874 $contributionParams,
1875 [
1876 'contact_id' => $contactId_1,
1877 'source_contact_id' => $contactId_3,
1878 ]
1879 ))['values'][0];
1880
1881 $activity = $this->callAPISuccessGetSingle('Activity', ['source_record_id' => $contribution['id']]);
1882
1883 $activityContactParams = [
1884 'activity_id' => $activity['id'],
1885 'record_type_id' => 'Activity Targets',
1886 ];
1887
1888 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1889
1890 $this->assertEquals($activityContact['contact_id'], $contactId_1, 'Check target contact ID matches first contact');
1891
1892 $contribution = $this->callAPISuccess('Contribution', 'create', array_merge(
1893 $contributionParams,
1894 [
1895 'id' => $contribution['id'],
1896 'contact_id' => $contactId_2,
1897 ]
1898 ))['values'][0];
1899
1900 $activityContact = $this->callAPISuccessGetSingle('ActivityContact', $activityContactParams);
1901
1902 $this->assertEquals($activityContact['contact_id'], $contactId_2, 'Check target contact ID matches the second contact');
1903 }
1904
1905 }