Merge pull request #4820 from kurund/CRM-15705
[civicrm-core.git] / CRM / Contribute / Import / Parser / Contribution.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * class to parse contribution csv files
38 */
39 class CRM_Contribute_Import_Parser_Contribution extends CRM_Contribute_Import_Parser {
40
41 protected $_mapperKeys;
42
43 private $_contactIdIndex;
44 private $_totalAmountIndex;
45 private $_contributionTypeIndex;
46
47 protected $_mapperSoftCredit;
48 //protected $_mapperPhoneType;
49
50 /**
51 * Array of successfully imported contribution id's
52 *
53 * @array
54 */
55 protected $_newContributions;
56
57 /**
58 * Class constructor
59 */
60 public function __construct(&$mapperKeys, $mapperSoftCredit = NULL, $mapperPhoneType = NULL, $mapperSoftCreditType = NULL) {
61 parent::__construct();
62 $this->_mapperKeys = &$mapperKeys;
63 $this->_mapperSoftCredit = &$mapperSoftCredit;
64 $this->_mapperSoftCreditType = &$mapperSoftCreditType;
65 }
66
67 /**
68 * The initializer code, called before the processing
69 *
70 * @return void
71 */
72 public function init() {
73 $fields = CRM_Contribute_BAO_Contribution::importableFields($this->_contactType, FALSE);
74
75 $fields = array_merge($fields,
76 array(
77 'soft_credit' => array(
78 'title' => ts('Soft Credit'),
79 'softCredit' => TRUE,
80 'headerPattern' => '/Soft Credit/i',
81 )
82 )
83 );
84
85 // add pledge fields only if its is enabled
86 if (CRM_Core_Permission::access('CiviPledge')) {
87 $pledgeFields = array(
88 'pledge_payment' => array(
89 'title' => ts('Pledge Payment'),
90 'headerPattern' => '/Pledge Payment/i',
91 ),
92 'pledge_id' => array(
93 'title' => ts('Pledge ID'),
94 'headerPattern' => '/Pledge ID/i',
95 ),
96 );
97
98 $fields = array_merge($fields, $pledgeFields);
99 }
100 foreach ($fields as $name => $field) {
101 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
102 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
103 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
104 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
105 }
106
107 $this->_newContributions = array();
108
109 $this->setActiveFields($this->_mapperKeys);
110 $this->setActiveFieldSoftCredit($this->_mapperSoftCredit);
111 $this->setActiveFieldSoftCreditType($this->_mapperSoftCreditType);
112
113 // FIXME: we should do this in one place together with Form/MapField.php
114 $this->_contactIdIndex = -1;
115 $this->_totalAmountIndex = -1;
116 $this->_contributionTypeIndex = -1;
117
118 $index = 0;
119 foreach ($this->_mapperKeys as $key) {
120 switch ($key) {
121 case 'contribution_contact_id':
122 $this->_contactIdIndex = $index;
123 break;
124
125 case 'total_amount':
126 $this->_totalAmountIndex = $index;
127 break;
128
129 case 'financial_type':
130 $this->_contributionTypeIndex = $index;
131 break;
132 }
133 $index++;
134 }
135 }
136
137 /**
138 * Handle the values in mapField mode
139 *
140 * @param array $values the array of values belonging to this line
141 *
142 * @return boolean
143 */
144 public function mapField(&$values) {
145 return CRM_Import_Parser::VALID;
146 }
147
148 /**
149 * Handle the values in preview mode
150 *
151 * @param array $values the array of values belonging to this line
152 *
153 * @return boolean the result of this processing
154 */
155 public function preview(&$values) {
156 return $this->summary($values);
157 }
158
159 /**
160 * Handle the values in summary mode
161 *
162 * @param array $values the array of values belonging to this line
163 *
164 * @return boolean the result of this processing
165 */
166 public function summary(&$values) {
167 $erroneousField = NULL;
168 $response = $this->setActiveFieldValues($values, $erroneousField);
169
170 $params = &$this->getActiveFieldParams();
171 $errorMessage = NULL;
172
173 //for date-Formats
174 $session = CRM_Core_Session::singleton();
175 $dateType = $session->get('dateTypes');
176 foreach ($params as $key => $val) {
177 if ($val) {
178 switch ($key) {
179 case 'receive_date':
180 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
181 $params[$key] = $dateValue;
182 }
183 else {
184 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receive Date', $errorMessage);
185 }
186 break;
187
188 case 'cancel_date':
189 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
190 $params[$key] = $dateValue;
191 }
192 else {
193 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Cancel Date', $errorMessage);
194 }
195 break;
196
197 case 'receipt_date':
198 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
199 $params[$key] = $dateValue;
200 }
201 else {
202 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Receipt date', $errorMessage);
203 }
204 break;
205
206 case 'thankyou_date':
207 if ($dateValue = CRM_Utils_Date::formatDate($params[$key], $dateType)) {
208 $params[$key] = $dateValue;
209 }
210 else {
211 CRM_Contact_Import_Parser_Contact::addToErrorMsg('Thankyou Date', $errorMessage);
212 }
213 break;
214 }
215 }
216 }
217 //date-Format part ends
218
219 $params['contact_type'] = 'Contribution';
220
221 //checking error in custom data
222 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
223
224 if ($errorMessage) {
225 $tempMsg = "Invalid value for field(s) : $errorMessage";
226 array_unshift($values, $tempMsg);
227 $errorMessage = NULL;
228 return CRM_Import_Parser::ERROR;
229 }
230
231 return CRM_Import_Parser::VALID;
232 }
233
234 /**
235 * Handle the values in import mode
236 *
237 * @param int $onDuplicate the code for what action to take on duplicates
238 * @param array $values the array of values belonging to this line
239 *
240 * @return boolean the result of this processing
241 */
242 public function import($onDuplicate, &$values) {
243 // first make sure this is a valid line
244 $response = $this->summary($values);
245 if ($response != CRM_Import_Parser::VALID) {
246 return $response;
247 }
248
249 $params = &$this->getActiveFieldParams();
250 $formatted = array('version' => 3);
251
252 // don't add to recent items, CRM-4399
253 $formatted['skipRecentView'] = TRUE;
254
255 //for date-Formats
256 $session = CRM_Core_Session::singleton();
257 $dateType = $session->get('dateTypes');
258
259 $customFields = CRM_Core_BAO_CustomField::getFields(CRM_Utils_Array::value('contact_type', $params));
260
261 //CRM-10994
262 if (isset($params['total_amount']) && $params['total_amount'] == 0) {
263 $params['total_amount'] = '0.00';
264 }
265 foreach ($params as $key => $val) {
266 if ($val) {
267 switch ($key) {
268 case 'receive_date':
269 case 'cancel_date':
270 case 'receipt_date':
271 case 'thankyou_date':
272 $params[$key] = CRM_Utils_Date::formatDate($params[$key], $dateType);
273 break;
274 case 'pledge_payment':
275 $params[$key] = CRM_Utils_String::strtobool($val);
276 break;
277 }
278 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
279 if ($customFields[$customFieldID]['data_type'] == 'Date') {
280 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $formatted, $dateType, $key);
281 unset($params[$key]);
282 }
283 elseif ($customFields[$customFieldID]['data_type'] == 'Boolean') {
284 $params[$key] = CRM_Utils_String::strtoboolstr($val);
285 }
286 }
287 }
288 }
289 //date-Format part ends
290
291 static $indieFields = NULL;
292 if ($indieFields == NULL) {
293 $tempIndieFields = CRM_Contribute_DAO_Contribution::import();
294 $indieFields = $tempIndieFields;
295 }
296
297 $paramValues = array();
298 foreach ($params as $key => $field) {
299 if ($field == NULL || $field === '') {
300 continue;
301 }
302 $paramValues[$key] = $field;
303 }
304
305 //import contribution record according to select contact type
306 if ($onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP &&
307 (!empty($paramValues['contribution_contact_id']) || !empty($paramValues['external_identifier']))
308 ) {
309 $paramValues['contact_type'] = $this->_contactType;
310 }
311 elseif ($onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE &&
312 (!empty($paramValues['contribution_id']) || !empty($values['trxn_id']) || !empty($paramValues['invoice_id']))
313 ) {
314 $paramValues['contact_type'] = $this->_contactType;
315 }
316 elseif (!empty($params['soft_credit'])) {
317 $paramValues['contact_type'] = $this->_contactType;
318 }
319 elseif (!empty($paramValues['pledge_payment'])) {
320 $paramValues['contact_type'] = $this->_contactType;
321 }
322
323 //need to pass $onDuplicate to check import mode.
324 if (!empty($paramValues['pledge_payment'])) {
325 $paramValues['onDuplicate'] = $onDuplicate;
326 }
327 require_once 'CRM/Utils/DeprecatedUtils.php';
328 $formatError = _civicrm_api3_deprecated_formatted_param($paramValues, $formatted, TRUE, $onDuplicate);
329
330 if ($formatError) {
331 array_unshift($values, $formatError['error_message']);
332 if (CRM_Utils_Array::value('error_data', $formatError) == 'soft_credit') {
333 return CRM_Contribute_Import_Parser::SOFT_CREDIT_ERROR;
334 }
335 elseif (CRM_Utils_Array::value('error_data', $formatError) == 'pledge_payment') {
336 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT_ERROR;
337 }
338 return CRM_Import_Parser::ERROR;
339 }
340
341 if ($onDuplicate != CRM_Import_Parser::DUPLICATE_UPDATE) {
342 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
343 CRM_Core_DAO::$_nullObject,
344 NULL,
345 'Contribution'
346 );
347 }
348 else {
349 //fix for CRM-2219 - Update Contribution
350 // onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE
351 if (!empty($paramValues['invoice_id']) || !empty($paramValues['trxn_id']) || !empty($paramValues['contribution_id'])) {
352 $dupeIds = array(
353 'id' => CRM_Utils_Array::value('contribution_id', $paramValues),
354 'trxn_id' => CRM_Utils_Array::value('trxn_id', $paramValues),
355 'invoice_id' => CRM_Utils_Array::value('invoice_id', $paramValues),
356 );
357
358 $ids['contribution'] = CRM_Contribute_BAO_Contribution::checkDuplicateIds($dupeIds);
359
360 if ($ids['contribution']) {
361 $formatted['id'] = $ids['contribution'];
362 $formatted['custom'] = CRM_Core_BAO_CustomField::postProcess($formatted,
363 CRM_Core_DAO::$_nullObject,
364 $formatted['id'],
365 'Contribution'
366 );
367 //process note
368 if (!empty($paramValues['note'])) {
369 $noteID = array();
370 $contactID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $ids['contribution'], 'contact_id');
371 $daoNote = new CRM_Core_BAO_Note();
372 $daoNote->entity_table = 'civicrm_contribution';
373 $daoNote->entity_id = $ids['contribution'];
374 if ($daoNote->find(TRUE)) {
375 $noteID['id'] = $daoNote->id;
376 }
377
378 $noteParams = array(
379 'entity_table' => 'civicrm_contribution',
380 'note' => $paramValues['note'],
381 'entity_id' => $ids['contribution'],
382 'contact_id' => $contactID,
383 );
384 CRM_Core_BAO_Note::add($noteParams, $noteID);
385 unset($formatted['note']);
386 }
387
388 //need to check existing soft credit contribution, CRM-3968
389 if (!empty($formatted['soft_credit'])) {
390 $dupeSoftCredit = array(
391 'contact_id' => $formatted['soft_credit'],
392 'contribution_id' => $ids['contribution'],
393 );
394
395 //Delete all existing soft Contribution from contribution_soft table for pcp_id is_null
396 $existingSoftCredit = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($dupeSoftCredit['contribution_id']);
397 if(isset($existingSoftCredit['soft_credit']) && !empty($existingSoftCredit['soft_credit'])){
398 foreach($existingSoftCredit['soft_credit'] as $key => $existingSoftCreditValues){
399 if (!empty($existingSoftCreditValues['soft_credit_id'])) {
400 $deleteParams = array(
401 'id' => $existingSoftCreditValues['soft_credit_id'],
402 'pcp_id' => NULL,
403 );
404 CRM_Contribute_BAO_ContributionSoft::del($deleteParams);
405 }
406 }
407 }
408 }
409
410 $newContribution = CRM_Contribute_BAO_Contribution::create($formatted, $ids);
411 $this->_newContributions[] = $newContribution->id;
412
413 //return soft valid since we need to show how soft credits were added
414 if (!empty($formatted['soft_credit'])) {
415 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
416 }
417
418 // process pledge payment assoc w/ the contribution
419 return self::processPledgePayments($formatted);
420
421 return CRM_Import_Parser::VALID;
422 }
423 else {
424 $labels = array(
425 'id' => 'Contribution ID',
426 'trxn_id' => 'Transaction ID',
427 'invoice_id' => 'Invoice ID',
428 );
429 foreach ($dupeIds as $k => $v) {
430 if ($v) {
431 $errorMsg[] = "$labels[$k] $v";
432 }
433 }
434 $errorMsg = implode(' AND ', $errorMsg);
435 array_unshift($values, 'Matching Contribution record not found for ' . $errorMsg . '. Row was skipped.');
436 return CRM_Import_Parser::ERROR;
437 }
438 }
439 }
440
441 if ($this->_contactIdIndex < 0) {
442 // set the contact type if its not set
443 if (!isset($paramValues['contact_type'])) {
444 $paramValues['contact_type'] = $this->_contactType;
445 }
446
447 $paramValues['version'] = 3;
448 //retrieve contact id using contact dedupe rule
449 require_once 'CRM/Utils/DeprecatedUtils.php';
450 $error = _civicrm_api3_deprecated_check_contact_dedupe($paramValues);
451
452 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
453 $matchedIDs = explode(',', $error['error_message']['params'][0]);
454 if (count($matchedIDs) > 1) {
455 array_unshift($values, 'Multiple matching contact records detected for this row. The contribution was not imported');
456 return CRM_Import_Parser::ERROR;
457 }
458 else {
459 $cid = $matchedIDs[0];
460 $formatted['contact_id'] = $cid;
461
462 $newContribution = civicrm_api('contribution', 'create', $formatted);
463 if (civicrm_error($newContribution)) {
464 if (is_array($newContribution['error_message'])) {
465 array_unshift($values, $newContribution['error_message']['message']);
466 if ($newContribution['error_message']['params'][0]) {
467 return CRM_Import_Parser::DUPLICATE;
468 }
469 }
470 else {
471 array_unshift($values, $newContribution['error_message']);
472 return CRM_Import_Parser::ERROR;
473 }
474 }
475
476 $this->_newContributions[] = $newContribution['id'];
477 $formatted['contribution_id'] = $newContribution['id'];
478
479 //return soft valid since we need to show how soft credits were added
480 if (!empty($formatted['soft_credit'])) {
481 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
482 }
483
484 // process pledge payment assoc w/ the contribution
485 return self::processPledgePayments($formatted);
486
487 return CRM_Import_Parser::VALID;
488 }
489 }
490 else {
491 // Using new Dedupe rule.
492 $ruleParams = array(
493 'contact_type' => $this->_contactType,
494 'used' => 'Unsupervised',
495 );
496 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
497 $disp = NULL;
498 foreach ($fieldsArray as $value) {
499 if (array_key_exists(trim($value), $params)) {
500 $paramValue = $params[trim($value)];
501 if (is_array($paramValue)) {
502 $disp .= $params[trim($value)][0][trim($value)] . " ";
503 }
504 else {
505 $disp .= $params[trim($value)] . " ";
506 }
507 }
508 }
509
510 if (!empty($params['external_identifier'])) {
511 if ($disp) {
512 $disp .= "AND {$params['external_identifier']}";
513 }
514 else {
515 $disp = $params['external_identifier'];
516 }
517 }
518
519 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
520 return CRM_Import_Parser::ERROR;
521 }
522 }
523 else {
524 if (!empty($paramValues['external_identifier'])) {
525 $checkCid = new CRM_Contact_DAO_Contact();
526 $checkCid->external_identifier = $paramValues['external_identifier'];
527 $checkCid->find(TRUE);
528 if ($checkCid->id != $formatted['contact_id']) {
529 array_unshift($values, 'Mismatch of External identifier :' . $paramValues['external_identifier'] . ' and Contact Id:' . $formatted['contact_id']);
530 return CRM_Import_Parser::ERROR;
531 }
532 }
533 $newContribution = civicrm_api('contribution', 'create', $formatted);
534 if (civicrm_error($newContribution)) {
535 if (is_array($newContribution['error_message'])) {
536 array_unshift($values, $newContribution['error_message']['message']);
537 if ($newContribution['error_message']['params'][0]) {
538 return CRM_Import_Parser::DUPLICATE;
539 }
540 }
541 else {
542 array_unshift($values, $newContribution['error_message']);
543 return CRM_Import_Parser::ERROR;
544 }
545 }
546
547 $this->_newContributions[] = $newContribution['id'];
548 $formatted['contribution_id'] = $newContribution['id'];
549
550 //return soft valid since we need to show how soft credits were added
551 if (!empty($formatted['soft_credit'])) {
552 return CRM_Contribute_Import_Parser::SOFT_CREDIT;
553 }
554
555 // process pledge payment assoc w/ the contribution
556 return self::processPledgePayments($formatted);
557
558 return CRM_Import_Parser::VALID;
559 }
560 }
561
562 /**
563 * Process pledge payments
564 */
565 public function processPledgePayments(&$formatted) {
566 if (!empty($formatted['pledge_payment_id']) && !empty($formatted['pledge_id'])) {
567 //get completed status
568 $completeStatusID = CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name');
569
570 //need to update payment record to map contribution_id
571 CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $formatted['pledge_payment_id'],
572 'contribution_id', $formatted['contribution_id']
573 );
574
575 CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($formatted['pledge_id'],
576 array($formatted['pledge_payment_id']),
577 $completeStatusID,
578 NULL,
579 $formatted['total_amount']
580 );
581
582 return CRM_Contribute_Import_Parser::PLEDGE_PAYMENT;
583 }
584 }
585
586 /**
587 * Get the array of successfully imported contribution id's
588 *
589 * @return array
590 */
591 public function &getImportedContributions() {
592 return $this->_newContributions;
593 }
594
595 /**
596 * The initializer code, called before the processing
597 *
598 * @return void
599 */
600 public function fini() {}
601 }