Add unit tests to cover date field imports
[civicrm-core.git] / CRM / Batch / Form / Entry.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035
TO
9 +--------------------------------------------------------------------+
10 */
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
9b161ac5
EM
18use Civi\Api4\Contribution;
19
6a488035 20/**
ce064e4f 21 * This class provides the functionality for batch entry for contributions/memberships.
6a488035
TO
22 */
23class CRM_Batch_Form_Entry extends CRM_Core_Form {
24
25 /**
eceb18cc 26 * Maximum profile fields that will be displayed.
abd76e0d 27 *
62d3ee27 28 * @var int
6a488035
TO
29 */
30 protected $_rowCount = 1;
31
32 /**
eceb18cc 33 * Batch id.
abd76e0d 34 *
62d3ee27 35 * @var int
6a488035
TO
36 */
37 protected $_batchId;
38
39 /**
eceb18cc 40 * Batch information.
abd76e0d 41 *
62d3ee27 42 * @var array
6a488035 43 */
be2fb01f 44 protected $_batchInfo = [];
6a488035
TO
45
46 /**
eceb18cc 47 * Store the profile id associated with the batch type.
62d3ee27 48 * @var int
6a488035
TO
49 */
50 protected $_profileId;
51
52 public $_action;
8ef12e64 53
6a488035
TO
54 public $_mode;
55
56 public $_params;
57
6a488035 58 /**
fe482240 59 * When not to reset sort_name.
abd76e0d 60 *
62d3ee27 61 * @var bool
6a488035
TO
62 */
63 protected $_preserveDefault = TRUE;
64
d6dd2bee
EM
65 /**
66 * Renew option for current row.
67 *
68 * This is as set on the form.
69 *
70 * @var int
71 */
72 protected $currentRowIsRenewOption;
73
6a488035 74 /**
0880a9d0 75 * Contact fields.
abd76e0d 76 *
62d3ee27 77 * @var array
6a488035 78 */
be2fb01f 79 protected $_contactFields = [];
6a488035 80
afe349ef 81 /**
0880a9d0 82 * Fields array of fields in the batch profile.
abd76e0d 83 *
afe349ef 84 * (based on the uf_field table data)
85 * (this can't be protected as it is passed into the CRM_Contact_Form_Task_Batch::parseStreetAddress function
86 * (although a future refactoring might hopefully change that so it uses the api & the function is not
87 * required
abd76e0d 88 *
afe349ef 89 * @var array
90 */
be2fb01f 91 public $_fields = [];
353ffa53 92
9b161ac5
EM
93 /**
94 * @var int
95 */
96 protected $currentRowContributionID;
97
d6dd2bee
EM
98 /**
99 * Row being processed.
100 *
101 * @var array
102 */
103 protected $currentRow = [];
104
5e8c8bd6
EM
105 /**
106 * @var array
107 */
108 protected $currentRowExistingMembership;
109
9b161ac5
EM
110 /**
111 * Get the contribution id for the current row.
112 *
113 * @return int
114 * @throws \CRM_Core_Exception
115 */
116 public function getCurrentRowContributionID(): int {
117 if (!isset($this->currentRowContributionID)) {
118 $this->currentRowContributionID = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipPayment', $this->getCurrentRowMembershipID(), 'contribution_id', 'membership_id');
119 }
120 return $this->currentRowContributionID;
121 }
122
123 /**
124 * Set the contribution ID for the current row.
125 *
126 * @param int $currentRowContributionID
127 */
128 public function setCurrentRowContributionID(int $currentRowContributionID): void {
129 $this->currentRowContributionID = $currentRowContributionID;
130 }
131
132 /**
133 * @return mixed
134 */
135 public function getCurrentRowMembershipID() {
136 return $this->currentRowMembershipID;
137 }
138
502822e7
EM
139 /**
140 * Get the order (contribution) status for the current row.
141 */
142 protected function getCurrentRowPaymentStatus() {
143 return CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $this->currentRow['contribution_status_id']);
144 }
145
5e8c8bd6
EM
146 /**
147 * Get the contact ID for the current row.
148 *
149 * @return int
150 */
151 public function getCurrentRowContactID(): int {
152 return $this->currentRow['contact_id'];
153 }
154
155 /**
156 * Get the membership type ID for the current row.
157 *
158 * @return int
159 */
160 public function getCurrentRowMembershipTypeID(): int {
161 return $this->currentRow['membership_type_id'];
162 }
163
9b161ac5
EM
164 /**
165 * Set the membership id for the current row.
166 *
167 * @param int $currentRowMembershipID
168 */
169 public function setCurrentRowMembershipID(int $currentRowMembershipID): void {
170 $this->currentRowMembershipID = $currentRowMembershipID;
171 }
172
173 /**
174 * @var int
175 */
176 protected $currentRowMembershipID;
177
cdd71d6b 178 /**
179 * Monetary fields that may be submitted.
180 *
181 * These should get a standardised format in the beginPostProcess function.
182 *
183 * These fields are common to many forms. Some may override this.
62d3ee27 184 * @var array
cdd71d6b 185 */
186 protected $submittableMoneyFields = ['total_amount', 'net_amount', 'non_deductible_amount', 'fee_amount'];
187
6a488035 188 /**
eceb18cc 189 * Build all the data structures needed to build the form.
abd76e0d 190 *
191 * @throws \CRM_Core_Exception
8ef12e64 192 */
00be9182 193 public function preProcess() {
6a488035
TO
194 $this->_batchId = CRM_Utils_Request::retrieve('id', 'Positive', $this, TRUE);
195
196 $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'browse');
8ef12e64 197
6a488035 198 if (empty($this->_batchInfo)) {
be2fb01f 199 $params = ['id' => $this->_batchId];
6a488035
TO
200 CRM_Batch_BAO_Batch::retrieve($params, $this->_batchInfo);
201
57465e2a 202 $this->assign('batchTotal', !empty($this->_batchInfo['total']) ? $this->_batchInfo['total'] : NULL);
6a488035
TO
203 $this->assign('batchType', $this->_batchInfo['type_id']);
204
205 // get the profile id associted with this batch type
206 $this->_profileId = CRM_Batch_BAO_Batch::getProfileId($this->_batchInfo['type_id']);
207 }
aefb0b79 208 CRM_Core_Resources::singleton()
353ffa53 209 ->addScriptFile('civicrm', 'templates/CRM/Batch/Form/Entry.js', 1, 'html-header')
be2fb01f
CW
210 ->addSetting(['batch' => ['type_id' => $this->_batchInfo['type_id']]])
211 ->addSetting(['setting' => ['monetaryThousandSeparator' => CRM_Core_Config::singleton()->monetaryThousandSeparator]])
212 ->addSetting(['setting' => ['monetaryDecimalPoint' => CRM_Core_Config::singleton()->monetaryDecimalPoint]]);
111f65f4 213
a5dfa653 214 $this->assign('defaultCurrencySymbol', CRM_Core_BAO_Country::defaultCurrencySymbol());
871a8674
EM
215 // This could be updated to TRUE in the formRule
216 $this->addExpectedSmartyVariable('batchAmountMismatch');
217 // It is unclear where this is otherwise assigned but the template expects it.
218 $this->addExpectedSmartyVariable('contactFields');
219 // The not-always-present refresh button.
220 $this->addOptionalQuickFormElement('_qf_Batch_refresh');
6a488035
TO
221 }
222
cbd44e94 223 /**
bf48aa29 224 * Set Batch ID.
225 *
226 * @param int $id
cbd44e94 227 */
123fe8fe 228 public function setBatchID($id) {
229 $this->_batchId = $id;
230 }
231
6a488035 232 /**
eceb18cc 233 * Build the form object.
abd76e0d 234 *
235 * @throws \CRM_Core_Exception
6a488035 236 */
00be9182 237 public function buildQuickForm() {
6a488035 238 if (!$this->_profileId) {
e22ec653 239 CRM_Core_Error::statusBounce(ts('Profile for bulk data entry is missing.'));
6a488035
TO
240 }
241
242 $this->addElement('hidden', 'batch_id', $this->_batchId);
243
4a413eb6 244 $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
6a488035 245 // get the profile information
d556e751 246 if ($this->_batchInfo['type_id'] == $batchTypes['Contribution']) {
483cfbc4 247 $this->setTitle(ts('Batch Data Entry for Contributions'));
6a488035 248 }
691df66d 249 elseif ($this->_batchInfo['type_id'] == $batchTypes['Membership']) {
483cfbc4 250 $this->setTitle(ts('Batch Data Entry for Memberships'));
6a488035 251 }
691df66d 252 elseif ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment']) {
483cfbc4 253 $this->setTitle(ts('Batch Data Entry for Pledge Payments'));
691df66d 254 }
abd76e0d 255
6a488035
TO
256 $this->_fields = CRM_Core_BAO_UFGroup::getFields($this->_profileId, FALSE, CRM_Core_Action::VIEW);
257
258 // remove file type field and then limit fields
259 $suppressFields = FALSE;
6a488035 260 foreach ($this->_fields as $name => $field) {
6454484c 261 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) && $this->_fields[$name]['html_type'] == 'Autocomplete-Select') {
6a488035
TO
262 $suppressFields = TRUE;
263 unset($this->_fields[$name]);
264 }
265
266 //fix to reduce size as we are using this field in grid
267 if (is_array($field['attributes']) && $this->_fields[$name]['attributes']['size'] > 19) {
268 //shrink class to "form-text-medium"
269 $this->_fields[$name]['attributes']['size'] = 19;
270 }
271 }
272
be2fb01f 273 $this->addFormRule(['CRM_Batch_Form_Entry', 'formRule'], $this);
6a488035
TO
274
275 // add the force save button
276 $forceSave = $this->getButtonName('upload', 'force');
277
cbd83dde 278 $this->addElement('xbutton',
6a488035 279 $forceSave,
cbd83dde 280 ts('Ignore Mismatch & Process the Batch?'),
f7083334
AH
281 [
282 'type' => 'submit',
283 'value' => 1,
57c59c34 284 'class' => 'crm-button crm-button_qf_Entry_upload_force-save',
f7083334 285 ]
6a488035
TO
286 );
287
be2fb01f 288 $this->addButtons([
5d4fcf54
TO
289 [
290 'type' => 'upload',
291 'name' => ts('Validate & Process the Batch'),
292 'isDefault' => TRUE,
293 ],
294 [
295 'type' => 'cancel',
296 'name' => ts('Save & Continue Later'),
297 ],
298 ]);
6a488035
TO
299
300 $this->assign('rowCount', $this->_batchInfo['item_count'] + 1);
301
be2fb01f 302 $preserveDefaultsArray = [
353ffa53
TO
303 'first_name',
304 'last_name',
305 'middle_name',
6a488035
TO
306 'organization_name',
307 'household_name',
be2fb01f 308 ];
6a488035 309
f1b78592 310 $contactTypes = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes(TRUE));
be2fb01f 311 $contactReturnProperties = [];
5056dc8e 312
6a488035 313 for ($rowNumber = 1; $rowNumber <= $this->_batchInfo['item_count']; $rowNumber++) {
be2fb01f 314 $this->addEntityRef("primary_contact_id[{$rowNumber}]", '', [
5d4fcf54
TO
315 'create' => TRUE,
316 'placeholder' => ts('- select -'),
317 ]);
6a488035
TO
318
319 // special field specific to membership batch udpate
320 if ($this->_batchInfo['type_id'] == 2) {
be2fb01f 321 $options = [
6a488035
TO
322 1 => ts('Add Membership'),
323 2 => ts('Renew Membership'),
be2fb01f 324 ];
6a488035
TO
325 $this->add('select', "member_option[$rowNumber]", '', $options);
326 }
00f6e1a8 327 if ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment']) {
4962f783 328 $options = ['' => ts('-select-')];
be2fb01f 329 $optionTypes = [
04e6444d 330 '1' => ts('Adjust Pledge Payment Schedule?'),
331 '2' => ts('Adjust Total Pledge Amount?'),
be2fb01f 332 ];
8e2b1206 333 $this->add('select', "option_type[$rowNumber]", NULL, $optionTypes);
334 if (!empty($this->_batchId) && !empty($this->_batchInfo['data']) && !empty($rowNumber)) {
335 $dataValues = json_decode($this->_batchInfo['data'], TRUE);
5542b7c2 336 if (!empty($dataValues['values']['primary_contact_id'][$rowNumber])) {
337 $pledgeIDs = CRM_Pledge_BAO_Pledge::getContactPledges($dataValues['values']['primary_contact_id'][$rowNumber]);
338 foreach ($pledgeIDs as $pledgeID) {
339 $pledgePayment = CRM_Pledge_BAO_PledgePayment::getOldestPledgePayment($pledgeID);
be2fb01f 340 $options += [$pledgeID => CRM_Utils_Date::customFormat($pledgePayment['schedule_date'], '%m/%d/%Y') . ', ' . $pledgePayment['amount'] . ' ' . $pledgePayment['currency']];
5542b7c2 341 }
8e2b1206 342 }
343 }
5056dc8e 344
b8e805eb 345 $this->add('select', "open_pledges[$rowNumber]", '', $options);
691df66d 346 }
8e2b1206 347
6a488035
TO
348 foreach ($this->_fields as $name => $field) {
349 if (in_array($field['field_type'], $contactTypes)) {
57465e2a 350 $fld = explode('-', $field['name']);
5b1666f7 351 $contactReturnProperties[$field['name']] = $fld[0];
6a488035
TO
352 }
353 CRM_Core_BAO_UFGroup::buildProfile($this, $field, NULL, NULL, FALSE, FALSE, $rowNumber);
354
355 if (in_array($field['name'], $preserveDefaultsArray)) {
356 $this->_preserveDefault = FALSE;
357 }
358 }
359 }
75140351 360
3f8a2e90 361 // CRM-19477: Display Error for Batch Sizes Exceeding php.ini max_input_vars
362 // Notes: $this->_elementIndex gives an approximate count of the variables being sent
75140351 363 // An offset value is set to deal with additional vars that are likely passed.
3f8a2e90 364 // There may be a more accurate way to do this...
5d4fcf54
TO
365 // set an offset to account for other vars we are not counting
366 $offset = 50;
75140351 367 if ((count($this->_elementIndex) + $offset) > ini_get("max_input_vars")) {
6dabf459 368 // Avoiding 'ts' for obscure messages.
507bc932 369 CRM_Core_Error::statusBounce(ts('Batch size is too large. Increase value of php.ini setting "max_input_vars" (current val = %1)', [1 => ini_get("max_input_vars")]));
49accad3 370 }
75140351 371
6a488035 372 $this->assign('fields', $this->_fields);
57465e2a 373 CRM_Core_Resources::singleton()
be2fb01f
CW
374 ->addSetting([
375 'contact' => [
353ffa53
TO
376 'return' => implode(',', $contactReturnProperties),
377 'fieldmap' => array_flip($contactReturnProperties),
be2fb01f
CW
378 ],
379 ]);
6a488035
TO
380
381 // don't set the status message when form is submitted.
382 $buttonName = $this->controller->getButtonName('submit');
383
384 if ($suppressFields && $buttonName != '_qf_Entry_next') {
8f878cd3 385 CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple records."), ts('Some Fields Excluded'), 'info');
6a488035
TO
386 }
387 }
388
389 /**
eceb18cc 390 * Form validations.
6a488035 391 *
82d480a5
TO
392 * @param array $params
393 * Posted values of the form.
394 * @param array $files
395 * List of errors to be posted back to the form.
1620f0bc 396 * @param \CRM_Batch_Form_Entry $self
82d480a5 397 * Form object.
6a488035 398 *
a6c01b45
CW
399 * @return array
400 * list of errors to be posted back to the form
6a488035 401 */
00be9182 402 public static function formRule($params, $files, $self) {
be2fb01f 403 $errors = [];
4a413eb6 404 $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
be2fb01f 405 $fields = [
4db803dd
CW
406 'total_amount' => ts('Amount'),
407 'financial_type' => ts('Financial Type'),
408 'payment_instrument' => ts('Payment Method'),
be2fb01f 409 ];
6a488035 410
0576b84b
SB
411 //CRM-16480 if contact is selected, validate financial type and amount field.
412 foreach ($params['field'] as $key => $value) {
47087bdc 413 if (isset($value['trxn_id'])) {
be2fb01f 414 if (0 < CRM_Core_DAO::singleValueQuery('SELECT id FROM civicrm_contribution WHERE trxn_id = %1', [1 => [$value['trxn_id'], 'String']])) {
47087bdc 415 $errors["field[$key][trxn_id]"] = ts('Transaction ID must be unique within the database');
416 }
417 }
61f45887 418 foreach ($fields as $field => $label) {
dddb4c8c 419 if (!empty($params['primary_contact_id'][$key]) && empty($value[$field])) {
be2fb01f 420 $errors["field[$key][$field]"] = ts('%1 is a required field.', [1 => $label]);
dddb4c8c 421 }
0576b84b
SB
422 }
423 }
424
a7488080 425 if (!empty($params['_qf_Entry_upload_force'])) {
0576b84b
SB
426 if (!empty($errors)) {
427 return $errors;
428 }
6a488035
TO
429 return TRUE;
430 }
431
432 $batchTotal = 0;
433 foreach ($params['field'] as $key => $value) {
b5eec332 434 $batchTotal += ($value['total_amount'] ?: 0);
6a488035 435
5ee60152 436 //validate for soft credit fields
ccec9d6b 437 if (!empty($params['soft_credit_contact_id'][$key]) && empty($params['soft_credit_amount'][$key])) {
d1401e86 438 $errors["soft_credit_amount[$key]"] = ts('Please enter the soft credit amount.');
5ee60152 439 }
8cc574cf 440 if (!empty($params['soft_credit_amount']) && !empty($params['soft_credit_amount'][$key]) && CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value($key, $params['soft_credit_amount'])) > CRM_Utils_Rule::cleanMoney($value['total_amount'])) {
5ee60152
RN
441 $errors["soft_credit_amount[$key]"] = ts('Soft credit amount should not be greater than the total amount');
442 }
8ef12e64 443
6a488035 444 //membership type is required for membership batch entry
5056dc8e 445 if ($self->_batchInfo['type_id'] == $batchTypes['Membership']) {
a7488080 446 if (empty($value['membership_type'][1])) {
6a488035
TO
447 $errors["field[$key][membership_type]"] = ts('Membership type is a required field.');
448 }
449 }
450 }
b8e805eb 451 if ($self->_batchInfo['type_id'] == $batchTypes['Pledge Payment']) {
5056dc8e 452 foreach (array_unique($params["open_pledges"]) as $value) {
42724136 453 if (!empty($value)) {
454 $duplicateRows = array_keys($params["open_pledges"], $value);
455 }
456 if (!empty($duplicateRows) && count($duplicateRows) > 1) {
5056dc8e 457 foreach ($duplicateRows as $key) {
7f6a92ef 458 $errors["open_pledges[$key]"] = ts('You can not record two payments for the same pledge in a single batch.');
5056dc8e 459 }
460 }
461 }
462 }
28e8aa1b 463 if ((string) $batchTotal != $self->_batchInfo['total']) {
6a488035
TO
464 $self->assign('batchAmountMismatch', TRUE);
465 $errors['_qf_defaults'] = ts('Total for amounts entered below does not match the expected batch total.');
466 }
467
468 if (!empty($errors)) {
469 return $errors;
470 }
6a488035
TO
471 return TRUE;
472 }
473
474 /**
eceb18cc 475 * Override default cancel action.
6a488035 476 */
00be9182 477 public function cancelAction() {
6a488035
TO
478 // redirect to batch listing
479 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/batch', 'reset=1'));
480 CRM_Utils_System::civiExit();
481 }
482
483 /**
c490a46a 484 * Set default values for the form.
abd76e0d 485 *
486 * @throws \CRM_Core_Exception
6a488035 487 */
00be9182 488 public function setDefaultValues() {
6a488035
TO
489 if (empty($this->_fields)) {
490 return;
491 }
492
493 // for add mode set smart defaults
481a74f4 494 if ($this->_action & CRM_Core_Action::ADD) {
3d927ee7 495 $currentDate = date('Y-m-d H-i-s');
6a488035 496
363544d7 497 $completeStatus = CRM_Contribute_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed');
be2fb01f 498 $specialFields = [
b2c9a0e3 499 'membership_join_date' => date('Y-m-d'),
6a488035 500 'receive_date' => $currentDate,
21dfd5f5 501 'contribution_status_id' => $completeStatus,
be2fb01f 502 ];
6a488035
TO
503
504 for ($rowNumber = 1; $rowNumber <= $this->_batchInfo['item_count']; $rowNumber++) {
481a74f4 505 foreach ($specialFields as $key => $value) {
8ef12e64 506 $defaults['field'][$rowNumber][$key] = $value;
6a488035 507 }
8ef12e64 508 }
6a488035
TO
509 }
510 else {
6c97864e 511 // get the cached info from data column of civicrm_batch
512 $data = CRM_Core_DAO::getFieldValue('CRM_Batch_BAO_Batch', $this->_batchId, 'data');
513 $defaults = json_decode($data, TRUE);
c0d307ec 514 $defaults = $defaults['values'];
6a488035 515 }
6c97864e 516
6a488035
TO
517 return $defaults;
518 }
519
520 /**
eceb18cc 521 * Process the form after the input has been submitted and validated.
abd76e0d 522 *
523 * @throws \CRM_Core_Exception
524 * @throws \CiviCRM_API3_Exception
6a488035
TO
525 */
526 public function postProcess() {
527 $params = $this->controller->exportValues($this->_name);
6a488035 528 $params['actualBatchTotal'] = 0;
d556e751 529
530 // get the profile information
be2fb01f
CW
531 $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
532 if (in_array($this->_batchInfo['type_id'], [$batchTypes['Pledge Payment'], $batchTypes['Contribution']])) {
6a488035
TO
533 $this->processContribution($params);
534 }
d556e751 535 elseif ($this->_batchInfo['type_id'] == $batchTypes['Membership']) {
818f20f0 536 $params['actualBatchTotal'] = $this->processMembership($params);
6a488035 537 }
d556e751 538
6a488035 539 // update batch to close status
be2fb01f 540 $paramValues = [
6a488035
TO
541 'id' => $this->_batchId,
542 // close status
1a453756 543 'status_id' => CRM_Core_PseudoConstant::getKey('CRM_Batch_BAO_Batch', 'status_id', 'Closed'),
6a488035 544 'total' => $params['actualBatchTotal'],
be2fb01f 545 ];
6a488035
TO
546
547 CRM_Batch_BAO_Batch::create($paramValues);
548
6a488035
TO
549 // set success status
550 CRM_Core_Session::setStatus("", ts("Batch Processed."), "success");
551
552 CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/batch', 'reset=1'));
553 }
554
555 /**
eceb18cc 556 * Process contribution records.
6a488035 557 *
82d480a5
TO
558 * @param array $params
559 * Associated array of submitted values.
6a488035 560 *
ce064e4f 561 * @return bool
abd76e0d 562 *
563 * @throws \CRM_Core_Exception
564 * @throws \CiviCRM_API3_Exception
6a488035
TO
565 */
566 private function processContribution(&$params) {
6a488035 567
cdd71d6b 568 foreach ($this->submittableMoneyFields as $moneyField) {
569 foreach ($params['field'] as $index => $fieldValues) {
570 if (isset($fieldValues[$moneyField])) {
571 $params['field'][$index][$moneyField] = CRM_Utils_Rule::cleanMoney($params['field'][$index][$moneyField]);
572 }
573 }
574 }
575 $params['actualBatchTotal'] = CRM_Utils_Rule::cleanMoney($params['actualBatchTotal']);
6a488035 576 // get the price set associated with offline contribution record.
9da8dc8c 577 $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name');
578 $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
85020e43
EM
579 $priceFieldID = CRM_Price_BAO_PriceSet::getOnlyPriceFieldID($this->_priceSet);
580 $priceFieldValueID = CRM_Price_BAO_PriceSet::getOnlyPriceFieldValueID($this->_priceSet);
6a488035 581
6a488035
TO
582 if (isset($params['field'])) {
583 foreach ($params['field'] as $key => $value) {
584 // if contact is not selected we should skip the row
ccec9d6b 585 if (empty($params['primary_contact_id'][$key])) {
6a488035
TO
586 continue;
587 }
588
9c1bc317 589 $value['contact_id'] = $params['primary_contact_id'][$key] ?? NULL;
6a488035
TO
590
591 // update contact information
592 $this->updateContactInfo($value);
593
5ee60152 594 //build soft credit params
ccec9d6b
CW
595 if (!empty($params['soft_credit_contact_id'][$key]) && !empty($params['soft_credit_amount'][$key])) {
596 $value['soft_credit'][$key]['contact_id'] = $params['soft_credit_contact_id'][$key];
0689c15c 597 $value['soft_credit'][$key]['amount'] = CRM_Utils_Rule::cleanMoney($params['soft_credit_amount'][$key]);
9e1854a1 598
599 //CRM-15350: if soft-credit-type profile field is disabled or removed then
600 //we choose configured SCT default value
601 if (!empty($params['soft_credit_type'][$key])) {
602 $value['soft_credit'][$key]['soft_credit_type_id'] = $params['soft_credit_type'][$key];
603 }
604 else {
605 $value['soft_credit'][$key]['soft_credit_type_id'] = CRM_Core_OptionGroup::getDefaultValue("soft_credit_type");
606 }
6a488035
TO
607 }
608
b7714c80
AH
609 // Build PCP params
610 if (!empty($params['pcp_made_through_id'][$key])) {
611 $value['pcp']['pcp_made_through_id'] = $params['pcp_made_through_id'][$key];
612 $value['pcp']['pcp_display_in_roll'] = !empty($params['pcp_display_in_roll'][$key]);
613 if (!empty($params['pcp_roll_nickname'][$key])) {
614 $value['pcp']['pcp_roll_nickname'] = $params['pcp_roll_nickname'][$key];
615 }
616 if (!empty($params['pcp_personal_note'][$key])) {
617 $value['pcp']['pcp_personal_note'] = $params['pcp_personal_note'][$key];
618 }
619 }
620
6a488035 621 $value['custom'] = CRM_Core_BAO_CustomField::postProcess($value,
df1f662a 622 NULL,
6a488035
TO
623 'Contribution'
624 );
625
a7488080 626 if (!empty($value['send_receipt'])) {
6a488035
TO
627 $value['receipt_date'] = date('Y-m-d His');
628 }
2e4a81d5 629 // these translations & date handling are required because we are calling BAO directly rather than the api
be2fb01f 630 $fieldTranslations = [
2e4a81d5
EM
631 'financial_type' => 'financial_type_id',
632 'payment_instrument' => 'payment_instrument_id',
633 'contribution_source' => 'source',
634 'contribution_note' => 'note',
80a67e92 635 'contribution_check_number' => 'check_number',
be2fb01f 636 ];
2e4a81d5 637 foreach ($fieldTranslations as $formField => $baoField) {
5542b7c2 638 if (isset($value[$formField])) {
2e4a81d5
EM
639 $value[$baoField] = $value[$formField];
640 }
641 unset($value[$formField]);
6a488035
TO
642 }
643
644 $params['actualBatchTotal'] += $value['total_amount'];
6a488035
TO
645 $value['batch_id'] = $this->_batchId;
646 $value['skipRecentView'] = TRUE;
647
648 // build line item params
691df66d 649 $this->_priceSet['fields'][$priceFieldID]['options'][$priceFieldValueID]['amount'] = $value['total_amount'];
86bfa4f6 650 $value['price_' . $priceFieldID] = 1;
6a488035 651
be2fb01f 652 $lineItem = [];
9da8dc8c 653 CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], $value, $lineItem[$priceSetId]);
6a488035 654
c039f658 655 // @todo - stop setting amount level in this function & call the CRM_Price_BAO_PriceSet::getAmountLevel
656 // function to get correct amount level consistently. Remove setting of the amount level in
657 // CRM_Price_BAO_PriceSet::processAmount. Extend the unit tests in CRM_Price_BAO_PriceSetTest
658 // to cover all variants.
6a488035
TO
659 unset($value['amount_level']);
660
2e4a81d5 661 //CRM-11529 for back office transactions
8ef12e64 662 //when financial_type_id is passed in form, update the
2e4a81d5 663 //line items with the financial type selected in form
c039f658 664 // @todo - create a price set or price field per financial type & simply choose the appropriate
665 // price field rather than working around the fact that each price_field is supposed to have a financial
666 // type & we are allowing that to be overridden.
8cc574cf 667 if (!empty($value['financial_type_id']) && !empty($lineItem[$priceSetId])) {
6a488035
TO
668 foreach ($lineItem[$priceSetId] as &$values) {
669 $values['financial_type_id'] = $value['financial_type_id'];
670 }
671 }
672 $value['line_item'] = $lineItem;
2f8a402e 673
6a488035 674 //finally call contribution create for all the magic
1273d77c 675 $contribution = CRM_Contribute_BAO_Contribution::create($value);
2f8a402e 676 // This code to retrieve the contribution has been moved here from the contribution create
677 // api. It may not be required.
678 $titleFields = [
679 'contact_id',
680 'total_amount',
681 'currency',
682 'financial_type_id',
683 ];
684 $retrieveRequired = 0;
685 foreach ($titleFields as $titleField) {
686 if (!isset($contribution->$titleField)) {
687 $retrieveRequired = 1;
688 break;
689 }
690 }
691 if ($retrieveRequired == 1) {
692 $contribution->find(TRUE);
693 }
4a413eb6 694 $batchTypes = CRM_Core_PseudoConstant::get('CRM_Batch_DAO_Batch', 'type_id', ['flip' => 1], 'validate');
34b568c4 695 if (!empty($this->_batchInfo['type_id']) && ($this->_batchInfo['type_id'] == $batchTypes['Pledge Payment'])) {
04e6444d 696 $adjustTotalAmount = FALSE;
5056dc8e 697 if (isset($params['option_type'][$key])) {
698 if ($params['option_type'][$key] == 2) {
699 $adjustTotalAmount = TRUE;
700 }
04e6444d 701 }
ce70f330 702 $pledgeId = $params['open_pledges'][$key];
5056dc8e 703 if (is_numeric($pledgeId)) {
704 $result = CRM_Pledge_BAO_PledgePayment::getPledgePayments($pledgeId);
705 $pledgePaymentId = 0;
481a74f4 706 foreach ($result as $key => $values) {
5056dc8e 707 if ($values['status'] != 'Completed') {
708 $pledgePaymentId = $values['id'];
709 break;
710 }
04e6444d 711 }
5056dc8e 712 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $pledgePaymentId, 'contribution_id', $contribution->id);
713 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeId,
be2fb01f 714 [$pledgePaymentId],
5056dc8e 715 $contribution->contribution_status_id,
716 NULL,
717 $contribution->total_amount,
718 $adjustTotalAmount
719 );
04e6444d 720 }
04e6444d 721 }
5056dc8e 722
6a488035 723 //process premiums
a7488080 724 if (!empty($value['product_name'])) {
6a488035 725 if ($value['product_name'][0] > 0) {
818f20f0 726 [$products, $options] = CRM_Contribute_BAO_Premium::getPremiumProductInfo();
6a488035
TO
727
728 $value['hidden_Premium'] = 1;
729 $value['product_option'] = CRM_Utils_Array::value(
730 $value['product_name'][1],
731 $options[$value['product_name'][0]]
732 );
733
be2fb01f 734 $premiumParams = [
6a488035
TO
735 'product_id' => $value['product_name'][0],
736 'contribution_id' => $contribution->id,
737 'product_option' => $value['product_option'],
738 'quantity' => 1,
be2fb01f 739 ];
6a488035
TO
740 CRM_Contribute_BAO_Contribution::addPremium($premiumParams);
741 }
742 }
743 // end of premium
744
745 //send receipt mail.
5056dc8e 746 if ($contribution->id && !empty($value['send_receipt'])) {
ac4760c5 747 $value['from_email_address'] = $this->getFromEmailAddress();
691df66d 748 $value['contribution_id'] = $contribution->id;
43f77a2c 749 if (!empty($value['soft_credit'])) {
750 $value = array_merge($value, CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id));
751 }
481a74f4 752 CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $value);
6a488035
TO
753 }
754 }
755 }
2e4a81d5 756 return TRUE;
6a488035 757 }
6a488035
TO
758
759 /**
eceb18cc 760 * Process membership records.
6a488035 761 *
82d480a5 762 * @param array $params
818f20f0 763 * Array of submitted values.
6a488035 764 *
818f20f0 765 * @return float
766 * batch total monetary amount.
6a488035 767 *
818f20f0 768 * @throws \CRM_Core_Exception
769 * @throws \CiviCRM_API3_Exception
77274636 770 * @throws \API_Exception
6a488035 771 */
818f20f0 772 private function processMembership(array $params) {
773 $batchTotal = 0;
fd4b9293 774 // get the price set associated with offline membership
9da8dc8c 775 $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_membership_type_amount', 'id', 'name');
776 $this->_priceSet = $priceSets = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId));
6a488035
TO
777
778 if (isset($params['field'])) {
04b40eab 779 // @todo - most of the wrangling in this function is because the api is not being used, especially date stuff.
6a488035 780 foreach ($params['field'] as $key => $value) {
9b161ac5
EM
781 // if contact is not selected we should skip the row
782 if (empty($params['primary_contact_id'][$key])) {
783 continue;
784 }
785 $value['contact_id'] = $params['primary_contact_id'][$key];
d2fc4c00 786 $value = $this->standardiseRow($value);
d6dd2bee 787 $this->currentRow = $value;
5e8c8bd6 788 $this->currentRowExistingMembership = NULL;
d6dd2bee 789 $this->currentRowIsRenewOption = (int) ($params['member_option'][$key] ?? 1);
6a488035
TO
790
791 // update contact information
792 $this->updateContactInfo($value);
793
6a488035 794 //check for custom data
502822e7 795 $value['custom'] = CRM_Core_BAO_CustomField::postProcess($this->currentRow,
6a488035
TO
796 $key,
797 'Membership',
d2fc4c00 798 $value['membership_type_id']
6a488035
TO
799 );
800
6a488035 801 // handle soft credit
9e1854a1 802 if (!empty($params['soft_credit_contact_id'][$key]) && !empty($params['soft_credit_amount'][$key])) {
ccec9d6b 803 $value['soft_credit'][$key]['contact_id'] = $params['soft_credit_contact_id'][$key];
0689c15c 804 $value['soft_credit'][$key]['amount'] = CRM_Utils_Rule::cleanMoney($params['soft_credit_amount'][$key]);
9e1854a1 805
806 //CRM-15350: if soft-credit-type profile field is disabled or removed then
807 //we choose Gift as default value as per Gift Membership rule
808 if (!empty($params['soft_credit_type'][$key])) {
809 $value['soft_credit'][$key]['soft_credit_type_id'] = $params['soft_credit_type'][$key];
810 }
811 else {
1a453756 812 $value['soft_credit'][$key]['soft_credit_type_id'] = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', 'Gift');
9e1854a1 813 }
6a488035 814 }
818f20f0 815 $batchTotal += $value['total_amount'];
6a488035
TO
816 $value['batch_id'] = $this->_batchId;
817 $value['skipRecentView'] = TRUE;
818
77274636
EM
819 $order = new CRM_Financial_BAO_Order();
820 // We use the override total amount because we are dealing with a
821 // possibly tax_inclusive total, which is assumed for the override total.
2df49c85 822 $order->setOverrideTotalAmount((float) $value['total_amount']);
77274636
EM
823 $order->setLineItem([
824 'membership_type_id' => $value['membership_type_id'],
825 'financial_type_id' => $value['financial_type_id'],
826 ], $key);
502822e7 827 $order->setEntityParameters($this->getCurrentRowMembershipParams(), $key);
77274636
EM
828
829 if (!empty($order->getLineItems())) {
830 $value['lineItems'] = [$order->getPriceSetID() => $order->getPriceFieldIndexedLineItems()];
6a488035
TO
831 $value['processPriceSet'] = TRUE;
832 }
833 // end of contribution related section
d6dd2bee 834 if ($this->currentRowIsRenew()) {
61767a1d 835 // The following parameter setting may be obsolete.
6a488035 836 $this->_params = $params;
04b40eab 837
be2fb01f 838 $formDates = [
6b409353
CW
839 'end_date' => $value['membership_end_date'] ?? NULL,
840 'start_date' => $value['membership_start_date'] ?? NULL,
be2fb01f 841 ];
9cd37b8c
EM
842
843 $membership = $this->legacyProcessMembership(
844 $value['custom'], $formDates
6a488035
TO
845 );
846
847 // make contribution entry
be2fb01f 848 $contrbutionParams = array_merge($value, ['membership_id' => $membership->id]);
5471eacc 849 $contrbutionParams['skipCleanMoney'] = TRUE;
3febe800 850 // @todo - calling this from here is pretty hacky since it is called from membership.create anyway
851 // This form should set the correct params & not call this fn directly.
28cc62fc 852 CRM_Member_BAO_Membership::recordMembershipContribution($contrbutionParams);
9b161ac5 853 $this->setCurrentRowMembershipID($membership->id);
8ef12e64 854 }
6a488035 855 else {
502822e7
EM
856 $createdOrder = civicrm_api3('Order', 'create', [
857 'line_items' => $order->getLineItemForV3OrderApi(),
858 'receive_date' => $this->currentRow['receive_date'],
859 'check_number' => $this->currentRow['check_number'] ?? '',
860 'contact_id' => $this->getCurrentRowContactID(),
861 'batch_id' => $this->_batchId,
862 'financial_type_id' => $this->currentRow['financial_type_id'],
863 'payment_instrument_id' => $this->currentRow['payment_instrument_id'],
864 ]);
865 $this->currentRowContributionID = $createdOrder['id'];
866
867 $this->setCurrentRowMembershipID($createdOrder['values'][$this->getCurrentRowContributionID()]['line_item'][0]['entity_id']);
868 if ($this->getCurrentRowPaymentStatus() === 'Completed') {
869 civicrm_api3('Payment', 'create', [
870 'total_amount' => $order->getTotalAmount() + $order->getTotalTaxAmount(),
871 'check_number' => $this->currentRow['check_number'] ?? '',
872 'trxn_date' => $this->currentRow['receive_date'],
873 'trxn_id' => $this->currentRow['trxn_id'],
874 'payment_instrument_id' => $this->currentRow['payment_instrument_id'],
875 'contribution_id' => $this->getCurrentRowContributionID(),
876 'is_send_contribution_notification' => FALSE,
877 ]);
878 }
6a488035 879
502822e7
EM
880 if (in_array($this->getCurrentRowPaymentStatus(), ['Failed', 'Cancelled'])) {
881 Contribution::update()
882 ->addValue('contribution_status_id', $this->currentRow['contribution_status_id'])
883 ->addWhere('id', '=', $this->getCurrentRowContributionID())
884 ->execute();
885 }
886 }
6a488035 887 //process premiums
a7488080 888 if (!empty($value['product_name'])) {
6a488035 889 if ($value['product_name'][0] > 0) {
7ec3caf3 890 [$products, $options] = CRM_Contribute_BAO_Premium::getPremiumProductInfo();
6a488035
TO
891
892 $value['hidden_Premium'] = 1;
893 $value['product_option'] = CRM_Utils_Array::value(
894 $value['product_name'][1],
895 $options[$value['product_name'][0]]
896 );
897
be2fb01f 898 $premiumParams = [
6a488035 899 'product_id' => $value['product_name'][0],
9b161ac5 900 'contribution_id' => $this->getCurrentRowContributionID(),
6a488035
TO
901 'product_option' => $value['product_option'],
902 'quantity' => 1,
be2fb01f 903 ];
6a488035
TO
904 CRM_Contribute_BAO_Contribution::addPremium($premiumParams);
905 }
906 }
907 // end of premium
908
909 //send receipt mail.
502822e7
EM
910 if ($this->getCurrentRowMembershipID() && !empty($value['send_receipt'])) {
911 $value['membership_id'] = $this->getCurrentRowMembershipID();
912 $this->emailReceipt($this, $value);
6a488035
TO
913 }
914 }
915 }
818f20f0 916 return $batchTotal;
6a488035
TO
917 }
918
7ec3caf3 919 /**
920 * Send email receipt.
921 *
922 * @param CRM_Core_Form $form
923 * Form object.
924 * @param array $formValues
7ec3caf3 925 *
926 * @return bool
927 * true if mail was sent successfully
ac4760c5 928 * @throws \CRM_Core_Exception|\API_Exception
7ec3caf3 929 *
7ec3caf3 930 */
502822e7
EM
931 protected function emailReceipt($form, &$formValues): bool {
932 $membership = new CRM_Member_BAO_Membership();
933 $membership->id = $this->getCurrentRowMembershipID();
934 $membership->fetch();
7ec3caf3 935 // @todo figure out how much of the stuff below is genuinely shared with the batch form & a logical shared place.
936 if (!empty($formValues['payment_instrument_id'])) {
937 $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
938 $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']];
939 }
940
941 $form->assign('module', 'Membership');
942 $form->assign('contactID', $formValues['contact_id']);
943
7ec3caf3 944 if (!empty($formValues['contribution_status_id'])) {
945 $form->assign('contributionStatusID', $formValues['contribution_status_id']);
946 $form->assign('contributionStatus', CRM_Contribute_PseudoConstant::contributionStatus($formValues['contribution_status_id'], 'name'));
947 }
948
d6dd2bee 949 $form->assign('receiptType', $this->currentRowIsRenew() ? 'membership renewal' : 'membership signup');
7ec3caf3 950 $form->assign('receive_date', CRM_Utils_Array::value('receive_date', $formValues));
951 $form->assign('formValues', $formValues);
952
953 $form->assign('mem_start_date', CRM_Utils_Date::formatDateOnlyLong($membership->start_date));
954 if (!CRM_Utils_System::isNull($membership->end_date)) {
955 $form->assign('mem_end_date', CRM_Utils_Date::formatDateOnlyLong($membership->end_date));
956 }
957 $form->assign('membership_name', CRM_Member_PseudoConstant::membershipType($membership->membership_type_id));
958
959 [$form->_contributorDisplayName, $form->_contributorEmail]
960 = CRM_Contact_BAO_Contact_Location::getEmailDetails($formValues['contact_id']);
961 $form->_receiptContactId = $formValues['contact_id'];
962
963 CRM_Core_BAO_MessageTemplate::sendTemplate(
964 [
7b2d6751 965 'workflow' => 'membership_offline_receipt',
ac4760c5 966 'from' => $this->getFromEmailAddress(),
7ec3caf3 967 'toName' => $form->_contributorDisplayName,
968 'toEmail' => $form->_contributorEmail,
969 'PDFFilename' => ts('receipt') . '.pdf',
0fba5cf8 970 'isEmailPdf' => Civi::settings()->get('invoice_is_email_pdf'),
7ec3caf3 971 'isTest' => (bool) ($form->_action & CRM_Core_Action::PREVIEW),
7b2d6751
EM
972 'modelProps' => [
973 'contributionId' => $this->getCurrentRowContributionID(),
974 'contactId' => $form->_receiptContactId,
975 'membershipId' => $this->getCurrentRowMembershipID(),
976 ],
7ec3caf3 977 ]
978 );
979
9b161ac5
EM
980 Contribution::update(FALSE)
981 ->addWhere('id', '=', $this->getCurrentRowContributionID())
982 ->setValues(['receipt_date', 'now'])
983 ->execute();
984
7ec3caf3 985 return TRUE;
986 }
987
6a488035 988 /**
eceb18cc 989 * Update contact information.
6a488035 990 *
82d480a5
TO
991 * @param array $value
992 * Associated array of submitted values.
6a488035 993 */
7ec3caf3 994 private function updateContactInfo(array &$value) {
6a488035
TO
995 $value['preserveDBName'] = $this->_preserveDefault;
996
997 //parse street address, CRM-7768
998 CRM_Contact_Form_Task_Batch::parseStreetAddress($value, $this);
999
1000 CRM_Contact_BAO_Contact::createProfileContact($value, $this->_fields,
1001 $value['contact_id']
1002 );
1003 }
cab024d4 1004
afe349ef 1005 /**
ce064e4f 1006 * Function exists purely for unit testing purposes.
1007 *
1008 * If you feel tempted to use this in live code then it probably means there is some functionality
1009 * that needs to be moved out of the form layer
cab024d4 1010 *
4c16123d 1011 * @param array $params
cab024d4
EM
1012 *
1013 * @return bool
afe349ef 1014 */
00be9182 1015 public function testProcessMembership($params) {
afe349ef 1016 return $this->processMembership($params);
1017 }
cab024d4
EM
1018
1019 /**
ce064e4f 1020 * Function exists purely for unit testing purposes.
1021 *
1022 * If you feel tempted to use this in live code then it probably means there is some functionality
1023 * that needs to be moved out of the form layer.
cab024d4
EM
1024 *
1025 * @param array $params
1026 *
1027 * @return bool
abd76e0d 1028 *
1029 * @throws \CRM_Core_Exception
1030 * @throws \CiviCRM_API3_Exception
cab024d4 1031 */
00be9182 1032 public function testProcessContribution($params) {
cab024d4
EM
1033 return $this->processContribution($params);
1034 }
96025800 1035
a1a9cf1f 1036 /**
a1a9cf1f 1037 * @param $customFieldsFormatted
a1a9cf1f 1038 * @param array $formDates
a1a9cf1f 1039 *
c92325a8
EM
1040 * @return CRM_Member_BAO_Membership
1041 *
a1a9cf1f
EM
1042 * @throws \CRM_Core_Exception
1043 * @throws \CiviCRM_API3_Exception
1044 */
9cd37b8c 1045 protected function legacyProcessMembership($customFieldsFormatted, $formDates = []): CRM_Member_DAO_Membership {
c92325a8 1046 $updateStatusId = FALSE;
ec40ee4a 1047 $changeToday = NULL;
ec40ee4a 1048 $numRenewTerms = 1;
a1a9cf1f 1049 $format = '%Y%m%d';
a1a9cf1f 1050 $ids = [];
95588dd4 1051 $isPayLater = NULL;
9cd37b8c 1052 $memParams = $this->getCurrentRowMembershipParams();
5e8c8bd6 1053 $currentMembership = $this->getCurrentMembership();
a1a9cf1f 1054
bf455864
EM
1055 // Now Renew the membership
1056 if (!$currentMembership['is_current_member']) {
1057 // membership is not CURRENT
a1a9cf1f 1058
bf455864
EM
1059 // CRM-7297 Membership Upsell - calculate dates based on new membership type
1060 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($currentMembership['id'],
1061 $changeToday,
1062 $this->getCurrentRowMembershipTypeID(),
1063 $numRenewTerms
1064 );
a1a9cf1f 1065
bf455864
EM
1066 foreach (['start_date', 'end_date'] as $dateType) {
1067 $memParams[$dateType] = $memParams[$dateType] ?: ($dates[$dateType] ?? NULL);
a1a9cf1f 1068 }
a1a9cf1f 1069
bf455864
EM
1070 $ids['membership'] = $currentMembership['id'];
1071
1072 //set the log start date.
1073 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
1074 }
1075 else {
1076
1077 // CURRENT Membership
1078 $membership = new CRM_Member_DAO_Membership();
1079 $membership->id = $currentMembership['id'];
1080 $membership->find(TRUE);
1081 // CRM-7297 Membership Upsell - calculate dates based on new membership type
1082 $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membership->id,
1083 $changeToday,
1084 $this->getCurrentRowMembershipTypeID(),
1085 $numRenewTerms
1086 );
1087
1088 // Insert renewed dates for CURRENT membership
1089 $memParams['join_date'] = CRM_Utils_Date::isoToMysql($membership->join_date);
1090 $memParams['start_date'] = $formDates['start_date'] ?? CRM_Utils_Date::isoToMysql($membership->start_date);
1091 $memParams['end_date'] = $formDates['end_date'] ?? NULL;
1092 if (empty($memParams['end_date'])) {
1093 $memParams['end_date'] = $dates['end_date'] ?? NULL;
1094 }
a1a9cf1f 1095
bf455864
EM
1096 //set the log start date.
1097 $memParams['log_start_date'] = CRM_Utils_Date::customFormat($dates['log_start_date'], $format);
a1a9cf1f 1098
bf455864
EM
1099 if (!empty($currentMembership['id'])) {
1100 $ids['membership'] = $currentMembership['id'];
a1a9cf1f 1101 }
bf455864 1102 $memParams['membership_activity_status'] = $isPayLater ? 'Scheduled' : 'Completed';
a1a9cf1f 1103 }
50002244 1104
a1a9cf1f
EM
1105 //CRM-4555
1106 //if we decided status here and want to skip status
1107 //calculation in create( ); then need to pass 'skipStatusCal'.
1108 if ($updateStatusId) {
1109 $memParams['status_id'] = $updateStatusId;
1110 $memParams['skipStatusCal'] = TRUE;
1111 }
1112
1113 //since we are renewing,
1114 //make status override false.
1115 $memParams['is_override'] = FALSE;
a1a9cf1f
EM
1116 $memParams['custom'] = $customFieldsFormatted;
1117 // Load all line items & process all in membership. Don't do in contribution.
1118 // Relevant tests in api_v3_ContributionPageTest.
a1a9cf1f
EM
1119 // @todo stop passing $ids (membership and userId may be set by this point)
1120 $membership = CRM_Member_BAO_Membership::create($memParams, $ids);
1121
1122 // not sure why this statement is here, seems quite odd :( - Lobo: 12/26/2010
1123 // related to: http://forum.civicrm.org/index.php/topic,11416.msg49072.html#msg49072
1124 $membership->find(TRUE);
1125
c92325a8 1126 return $membership;
a1a9cf1f
EM
1127 }
1128
ac4760c5
EM
1129 /**
1130 * @return string
1131 * @throws \CRM_Core_Exception
1132 */
1133 private function getFromEmailAddress(): string {
1134 $domainEmail = CRM_Core_BAO_Domain::getNameAndEmail();
1135 return "$domainEmail[0] <$domainEmail[1]>";
1136 }
1137
d2fc4c00
EM
1138 /**
1139 * Standardise the values in the row from profile-weirdness to civi-standard.
1140 *
1141 * The row uses odd field names such as financial_type rather than financial
1142 * type id. We standardise at this point.
1143 *
1144 * @param array $row
1145 *
1146 * @return array
1147 */
1148 private function standardiseRow(array $row): array {
d6dd2bee
EM
1149 foreach ($row as $fieldKey => $fieldValue) {
1150 if (isset($this->_fields[$fieldKey]) && $this->_fields[$fieldKey]['data_type'] === 'Money') {
1151 $row[$fieldKey] = CRM_Utils_Rule::cleanMoney($fieldValue);
1152 }
1153 }
d2fc4c00
EM
1154 $renameFieldMapping = [
1155 'financial_type' => 'financial_type_id',
1156 'payment_instrument' => 'payment_instrument_id',
1157 'membership_source' => 'source',
1158 'membership_status' => 'status_id',
1159 ];
1160 foreach ($renameFieldMapping as $weirdProfileName => $betterName) {
1161 // Check if isset as some like payment instrument and source are optional.
1162 if (isset($row[$weirdProfileName]) && empty($row[$betterName])) {
1163 $row[$betterName] = $row[$weirdProfileName];
1164 unset($row[$weirdProfileName]);
1165 }
1166 }
1167
1168 // The latter format would be normal here - it's unclear if it is sometimes in the former format.
1169 $row['membership_type_id'] = $row['membership_type_id'] ?? $row['membership_type'][1];
1170 unset($row['membership_type']);
1171 // total_amount is required.
1172 $row['total_amount'] = (float) $row['total_amount'];
1173 return $row;
1174 }
1175
d6dd2bee
EM
1176 /**
1177 * Is the current row a renewal.
1178 *
1179 * @return bool
c0f6a1d1
EM
1180 *
1181 * @throws \CRM_Core_Exception
1182 * @throws \CiviCRM_API3_Exception
d6dd2bee
EM
1183 */
1184 private function currentRowIsRenew(): bool {
c0f6a1d1 1185 return $this->currentRowIsRenewOption === 2 && $this->getCurrentMembership();
d6dd2bee
EM
1186 }
1187
5e8c8bd6
EM
1188 /**
1189 * Get any current membership for the current row contact, for the same member organization.
1190 *
1191 * @return array|bool
1192 *
1193 * @throws \CiviCRM_API3_Exception
1194 * @throws \CRM_Core_Exception
1195 */
1196 protected function getCurrentMembership() {
1197 if (!isset($this->currentRowExistingMembership)) {
1198 // CRM-7297 - allow membership type to be be changed during renewal so long as the parent org of new membershipType
1199 // is the same as the parent org of an existing membership of the contact
1200 $this->currentRowExistingMembership = CRM_Member_BAO_Membership::getContactMembership($this->getCurrentRowContactID(), $this->getCurrentRowMembershipTypeID(),
1201 FALSE, NULL, TRUE
1202 );
502822e7
EM
1203 if ($this->currentRowExistingMembership) {
1204 // Check and fix the membership if it is STALE
1205 CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($this->currentRowExistingMembership);
1206 }
5e8c8bd6
EM
1207 }
1208 return $this->currentRowExistingMembership;
1209 }
1210
6601aa51
EM
1211 /**
1212 * Get the params as related to the membership entity.
1213 *
1214 * @return array
1215 */
9cd37b8c 1216 private function getCurrentRowMembershipParams(): array {
502822e7 1217 return array_merge($this->getCurrentRowCustomParams(), [
6601aa51
EM
1218 'start_date' => $this->currentRow['membership_start_date'] ?? NULL,
1219 'end_date' => $this->currentRow['membership_end_date'] ?? NULL,
1220 'join_date' => $this->currentRow['membership_join_date'] ?? NULL,
1221 'campaign_id' => $this->currentRow['member_campaign_id'] ?? NULL,
502822e7
EM
1222 'source' => $this->currentRow['source'] ?? (!$this->currentRowIsRenew() ? ts('Batch entry') : ''),
1223 'membership_type_id' => $this->currentRow['membership_type_id'],
1224 'contact_id' => $this->getCurrentRowContactID(),
1225 ]);
1226 }
1227
1228 /**
1229 * Get the custom value parameters from the current row.
1230 *
1231 * @return array
1232 */
1233 private function getCurrentRowCustomParams(): array {
1234 $return = [];
1235 foreach ($this->currentRow as $field => $value) {
1236 if (strpos($field, 'custom_') === 0) {
1237 $return[$field] = $value;
1238 }
1239 }
1240 return $return;
6601aa51
EM
1241 }
1242
6a488035 1243}