Fix Invoice class to not call validateData
[civicrm-core.git] / CRM / Contribute / Form / Task / Invoice.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
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class provides the functionality to email a group of
20 * contacts.
21 */
22 class CRM_Contribute_Form_Task_Invoice extends CRM_Contribute_Form_Task {
23 /**
24 * Are we operating in "single mode", i.e. updating the task of only
25 * one specific contribution?
26 *
27 * @var bool
28 */
29 public $_single = FALSE;
30
31 /**
32 * Gives all the statues for conribution.
33 * @var int
34 */
35 public $_contributionStatusId;
36
37 /**
38 * Gives the HTML template of PDF Invoice.
39 * @var string
40 */
41 public $_messageInvoice;
42
43 /**
44 * This variable is used to assign parameters for HTML template of PDF Invoice.
45 * @var string
46 */
47 public $_invoiceTemplate;
48
49 /**
50 * Selected output.
51 * @var string
52 */
53 public $_selectedOutput;
54
55 /**
56 * Build all the data structures needed to build the form.
57 */
58 public function preProcess() {
59 $id = CRM_Utils_Request::retrieve('id', 'Positive', $this, FALSE);
60 if ($id) {
61 $this->_contributionIds = [$id];
62 $this->_componentClause = " civicrm_contribution.id IN ( $id ) ";
63 $this->_single = TRUE;
64 $this->assign('totalSelectedContributions', 1);
65
66 // set the redirection after actions
67 $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, FALSE);
68 $url = CRM_Utils_System::url('civicrm/contact/view/contribution',
69 "action=view&reset=1&id={$id}&cid={$contactId}&context=contribution&selectedChild=contribute"
70 );
71
72 CRM_Core_Session::singleton()->pushUserContext($url);
73 }
74 else {
75 parent::preProcess();
76 }
77
78 // check that all the contribution ids have status Completed, Pending, Refunded, or Partially Paid.
79 $this->_contributionStatusId = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
80 $status = ['Completed', 'Pending', 'Refunded', 'Partially paid'];
81 $statusId = [];
82 foreach ($this->_contributionStatusId as $key => $value) {
83 if (in_array($value, $status)) {
84 $statusId[] = $key;
85 }
86 }
87 $Id = implode(",", $statusId);
88 $query = "SELECT count(*) FROM civicrm_contribution WHERE contribution_status_id NOT IN ($Id) AND {$this->_componentClause}";
89 $count = CRM_Core_DAO::singleValueQuery($query);
90 if ($count != 0) {
91 CRM_Core_Error::statusBounce(ts('Please select only contributions with Completed, Pending, Refunded, or Partially Paid status.'));
92 }
93
94 // we have all the contribution ids, so now we get the contact ids
95 parent::setContactIDs();
96 $this->assign('single', $this->_single);
97
98 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
99 $urlParams = 'force=1';
100 if (CRM_Utils_Rule::qfKey($qfKey)) {
101 $urlParams .= "&qfKey=$qfKey";
102 }
103
104 $url = CRM_Utils_System::url('civicrm/contribute/search', $urlParams);
105 $breadCrumb = [
106 [
107 'url' => $url,
108 'title' => ts('Search Results'),
109 ],
110 ];
111
112 CRM_Utils_System::appendBreadCrumb($breadCrumb);
113
114 $this->_selectedOutput = CRM_Utils_Request::retrieve('select', 'String', $this);
115 $this->assign('selectedOutput', $this->_selectedOutput);
116
117 CRM_Contact_Form_Task_EmailCommon::preProcessFromAddress($this);
118 if ($this->_selectedOutput == 'email') {
119 CRM_Utils_System::setTitle(ts('Email Invoice'));
120 }
121 else {
122 CRM_Utils_System::setTitle(ts('Print Contribution Invoice'));
123 }
124 }
125
126 /**
127 * Build the form object.
128 */
129 public function buildQuickForm() {
130 $this->preventAjaxSubmit();
131 if (CRM_Core_Permission::check('administer CiviCRM')) {
132 $this->assign('isAdmin', 1);
133 }
134
135 $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails, TRUE);
136 if ($this->_selectedOutput != 'email') {
137 $this->addElement('radio', 'output', NULL, ts('Email Invoice'), 'email_invoice');
138 $this->addElement('radio', 'output', NULL, ts('PDF Invoice'), 'pdf_invoice');
139 $this->addRule('output', ts('Selection required'), 'required');
140 $this->addFormRule(['CRM_Contribute_Form_Task_Invoice', 'formRule']);
141 }
142 else {
143 $this->addRule('from_email_address', ts('From Email Address is required'), 'required');
144 }
145
146 $this->add('wysiwyg', 'email_comment', ts('If you would like to add personal message to email please add it here. (If sending to more then one receipient the same message will be sent to each contact.)'), [
147 'rows' => 2,
148 'cols' => 40,
149 ]);
150
151 $this->addButtons([
152 [
153 'type' => 'upload',
154 'name' => $this->_selectedOutput == 'email' ? ts('Send Email') : ts('Process Invoice(s)'),
155 'isDefault' => TRUE,
156 ],
157 [
158 'type' => 'cancel',
159 'name' => ts('Cancel'),
160 ],
161 ]);
162 }
163
164 /**
165 * Global validation rules for the form.
166 *
167 * @param array $values
168 *
169 * @return array
170 * list of errors to be posted back to the form
171 */
172 public static function formRule($values) {
173 $errors = [];
174
175 if ($values['output'] == 'email_invoice' && empty($values['from_email_address'])) {
176 $errors['from_email_address'] = ts("From Email Address is required");
177 }
178
179 return $errors;
180 }
181
182 /**
183 * Process the form after the input has been submitted and validated.
184 */
185 public function postProcess() {
186 $params = $this->controller->exportValues($this->_name);
187 self::printPDF($this->_contributionIds, $params, $this->_contactIds);
188 }
189
190 /**
191 * Process the PDf and email with activity and attachment on click of Print Invoices.
192 *
193 * @param array $contribIDs
194 * Contribution Id.
195 * @param array $params
196 * Associated array of submitted values.
197 * @param array $contactIds
198 * Contact Id.
199 */
200 public static function printPDF($contribIDs, &$params, $contactIds) {
201 // get all the details needed to generate a invoice
202 $messageInvoice = [];
203 $invoiceTemplate = CRM_Core_Smarty::singleton();
204 $invoiceElements = CRM_Contribute_Form_Task_PDF::getElements($contribIDs, $params, $contactIds);
205
206 // gives the status id when contribution status is 'Refunded'
207 $contributionStatusID = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
208 $refundedStatusId = CRM_Utils_Array::key('Refunded', $contributionStatusID);
209 $cancelledStatusId = CRM_Utils_Array::key('Cancelled', $contributionStatusID);
210 $pendingStatusId = CRM_Utils_Array::key('Pending', $contributionStatusID);
211
212 foreach ($invoiceElements['details'] as $contribID => $detail) {
213 $input = $ids = [];
214 if (in_array($detail['contact'], $invoiceElements['excludeContactIds'])) {
215 continue;
216 }
217
218 $input['component'] = $detail['component'];
219
220 $ids['contact'] = $detail['contact'];
221 $ids['contribution'] = $contribID;
222 $ids['contributionRecur'] = NULL;
223 $ids['contributionPage'] = NULL;
224 $ids['membership'] = $detail['membership'] ?? NULL;
225 $ids['participant'] = $detail['participant'] ?? NULL;
226 $ids['event'] = $detail['event'] ?? NULL;
227
228 $contribution = new CRM_Contribute_BAO_Contribution();
229 $contribution->id = $contribID;
230 $contribution->fetch();
231 $contribution->loadRelatedObjects($input, $ids, TRUE);
232
233 $input['amount'] = $contribution->total_amount;
234 $input['invoice_id'] = $contribution->invoice_id;
235 $input['receive_date'] = $contribution->receive_date;
236 $input['contribution_status_id'] = $contribution->contribution_status_id;
237 $input['organization_name'] = $contribution->_relatedObjects['contact']->organization_name;
238
239 // Fetch the billing address. getValues should prioritize the billing
240 // address, otherwise will return the primary address.
241 $billingAddress = [];
242
243 $addressDetails = CRM_Core_BAO_Address::getValues([
244 'contact_id' => $contribution->contact_id,
245 'is_billing' => 1,
246 ]);
247
248 if (!empty($addressDetails)) {
249 $billingAddress = array_shift($addressDetails);
250 }
251
252 if ($contribution->contribution_status_id == $refundedStatusId || $contribution->contribution_status_id == $cancelledStatusId) {
253 $creditNoteId = $contribution->creditnote_id;
254 }
255 if (!$contribution->invoice_number) {
256 $contribution->invoice_number = CRM_Contribute_BAO_Contribution::getInvoiceNumber($contribution->id);
257 }
258
259 //to obtain due date for PDF invoice
260 $contributionReceiveDate = date('F j,Y', strtotime(date($input['receive_date'])));
261 $invoiceDate = date("F j, Y");
262 $dueDateSetting = Civi::settings()->get('invoice_due_date');
263 $dueDatePeriodSetting = Civi::settings()->get('invoice_due_date_period');
264 $dueDate = date('F j, Y', strtotime($contributionReceiveDate . "+" . $dueDateSetting . "" . $dueDatePeriodSetting));
265
266 $amountPaid = CRM_Core_BAO_FinancialTrxn::getTotalPayments($contribID, TRUE);
267 $amountDue = ($input['amount'] - $amountPaid);
268
269 // retrieving the subtotal and sum of same tax_rate
270 $dataArray = [];
271 $subTotal = 0;
272 $lineItem = CRM_Price_BAO_LineItem::getLineItemsByContributionID($contribID);
273 foreach ($lineItem as $taxRate) {
274 if (isset($dataArray[(string) $taxRate['tax_rate']])) {
275 $dataArray[(string) $taxRate['tax_rate']] = $dataArray[(string) $taxRate['tax_rate']] + CRM_Utils_Array::value('tax_amount', $taxRate);
276 }
277 else {
278 $dataArray[(string) $taxRate['tax_rate']] = $taxRate['tax_amount'] ?? NULL;
279 }
280 $subTotal += CRM_Utils_Array::value('subTotal', $taxRate);
281 }
282
283 // to email the invoice
284 $mailDetails = [];
285 $values = [];
286 if ($contribution->_component == 'event') {
287 $daoName = 'CRM_Event_DAO_Event';
288 $pageId = $contribution->_relatedObjects['event']->id;
289 $mailElements = [
290 'title',
291 'confirm_from_name',
292 'confirm_from_email',
293 ];
294 CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements);
295 $values['title'] = $mailDetails[$contribution->_relatedObjects['event']->id]['title'] ?? NULL;
296 $values['confirm_from_name'] = $mailDetails[$contribution->_relatedObjects['event']->id]['confirm_from_name'] ?? NULL;
297 $values['confirm_from_email'] = $mailDetails[$contribution->_relatedObjects['event']->id]['confirm_from_email'] ?? NULL;
298
299 $title = $mailDetails[$contribution->_relatedObjects['event']->id]['title'] ?? NULL;
300 }
301 elseif ($contribution->_component == 'contribute') {
302 $daoName = 'CRM_Contribute_DAO_ContributionPage';
303 $pageId = $contribution->contribution_page_id;
304 $mailElements = [
305 'title',
306 'receipt_from_name',
307 'receipt_from_email',
308 'cc_receipt',
309 'bcc_receipt',
310 ];
311 CRM_Core_DAO::commonRetrieveAll($daoName, 'id', $pageId, $mailDetails, $mailElements);
312
313 $values['title'] = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
314 $values['receipt_from_name'] = CRM_Utils_Array::value('receipt_from_name', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
315 $values['receipt_from_email'] = CRM_Utils_Array::value('receipt_from_email', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
316 $values['cc_receipt'] = CRM_Utils_Array::value('cc_receipt', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
317 $values['bcc_receipt'] = CRM_Utils_Array::value('bcc_receipt', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
318
319 $title = CRM_Utils_Array::value('title', CRM_Utils_Array::value($contribution->contribution_page_id, $mailDetails));
320 }
321 $source = $contribution->source;
322
323 $config = CRM_Core_Config::singleton();
324 if (!isset($params['forPage'])) {
325 $config->doNotAttachPDFReceipt = 1;
326 }
327
328 // get organization address
329 $domain = CRM_Core_BAO_Domain::getDomain();
330 $locParams = ['contact_id' => $domain->contact_id];
331 $locationDefaults = CRM_Core_BAO_Location::getValues($locParams);
332 if (isset($locationDefaults['address'][1]['state_province_id'])) {
333 $stateProvinceAbbreviationDomain = CRM_Core_PseudoConstant::stateProvinceAbbreviation($locationDefaults['address'][1]['state_province_id']);
334 }
335 else {
336 $stateProvinceAbbreviationDomain = '';
337 }
338 if (isset($locationDefaults['address'][1]['country_id'])) {
339 $countryDomain = CRM_Core_PseudoConstant::country($locationDefaults['address'][1]['country_id']);
340 }
341 else {
342 $countryDomain = '';
343 }
344
345 $invoiceNotes = Civi::settings()->get('invoice_notes') ?? NULL;
346
347 // parameters to be assign for template
348 $tplParams = [
349 'title' => $title,
350 'component' => $input['component'],
351 'id' => $contribution->id,
352 'source' => $source,
353 'invoice_number' => $contribution->invoice_number,
354 'invoice_id' => $contribution->invoice_id,
355 'resourceBase' => $config->userFrameworkResourceURL,
356 'defaultCurrency' => $config->defaultCurrency,
357 'amount' => $contribution->total_amount,
358 'amountDue' => $amountDue,
359 'amountPaid' => $amountPaid,
360 'invoice_date' => $invoiceDate,
361 'dueDate' => $dueDate,
362 'notes' => $invoiceNotes,
363 'display_name' => $contribution->_relatedObjects['contact']->display_name,
364 'lineItem' => $lineItem,
365 'dataArray' => $dataArray,
366 'refundedStatusId' => $refundedStatusId,
367 'pendingStatusId' => $pendingStatusId,
368 'cancelledStatusId' => $cancelledStatusId,
369 'contribution_status_id' => $contribution->contribution_status_id,
370 'contributionStatusName' => CRM_Core_PseudoConstant::getName('CRM_Contribute_BAO_Contribution', 'contribution_status_id', $contribution->contribution_status_id),
371 'subTotal' => $subTotal,
372 'street_address' => $billingAddress['street_address'] ?? NULL,
373 'supplemental_address_1' => $billingAddress['supplemental_address_1'] ?? NULL,
374 'supplemental_address_2' => $billingAddress['supplemental_address_2'] ?? NULL,
375 'supplemental_address_3' => $billingAddress['supplemental_address_3'] ?? NULL,
376 'city' => $billingAddress['city'] ?? NULL,
377 'postal_code' => $billingAddress['postal_code'] ?? NULL,
378 'state_province' => $billingAddress['state_province'] ?? NULL,
379 'state_province_abbreviation' => $billingAddress['state_province_abbreviation'] ?? NULL,
380 // Kept for backwards compatibility
381 'stateProvinceAbbreviation' => $billingAddress['state_province_abbreviation'] ?? NULL,
382 'country' => $billingAddress['country'] ?? NULL,
383 'is_pay_later' => $contribution->is_pay_later,
384 'organization_name' => $contribution->_relatedObjects['contact']->organization_name,
385 'domain_organization' => $domain->name,
386 'domain_street_address' => CRM_Utils_Array::value('street_address', CRM_Utils_Array::value('1', $locationDefaults['address'])),
387 'domain_supplemental_address_1' => CRM_Utils_Array::value('supplemental_address_1', CRM_Utils_Array::value('1', $locationDefaults['address'])),
388 'domain_supplemental_address_2' => CRM_Utils_Array::value('supplemental_address_2', CRM_Utils_Array::value('1', $locationDefaults['address'])),
389 'domain_supplemental_address_3' => CRM_Utils_Array::value('supplemental_address_3', CRM_Utils_Array::value('1', $locationDefaults['address'])),
390 'domain_city' => CRM_Utils_Array::value('city', CRM_Utils_Array::value('1', $locationDefaults['address'])),
391 'domain_postal_code' => CRM_Utils_Array::value('postal_code', CRM_Utils_Array::value('1', $locationDefaults['address'])),
392 'domain_state' => $stateProvinceAbbreviationDomain,
393 'domain_country' => $countryDomain,
394 'domain_email' => CRM_Utils_Array::value('email', CRM_Utils_Array::value('1', $locationDefaults['email'])),
395 'domain_phone' => CRM_Utils_Array::value('phone', CRM_Utils_Array::value('1', $locationDefaults['phone'])),
396 ];
397
398 if (isset($creditNoteId)) {
399 $tplParams['creditnote_id'] = $creditNoteId;
400 }
401
402 $pdfFileName = $contribution->invoice_number . ".pdf";
403 $sendTemplateParams = [
404 'groupName' => 'msg_tpl_workflow_contribution',
405 'valueName' => 'contribution_invoice_receipt',
406 'contactId' => $contribution->contact_id,
407 'tplParams' => $tplParams,
408 'PDFFilename' => $pdfFileName,
409 ];
410
411 // from email address
412 $fromEmailAddress = $params['from_email_address'] ?? NULL;
413
414 // condition to check for download PDF Invoice or email Invoice
415 if ($invoiceElements['createPdf']) {
416 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
417 if (isset($params['forPage'])) {
418 return $html;
419 }
420 else {
421 $mail = [
422 'subject' => $subject,
423 'body' => $message,
424 'html' => $html,
425 ];
426 if ($mail['html']) {
427 $messageInvoice[] = $mail['html'];
428 }
429 else {
430 $messageInvoice[] = nl2br($mail['body']);
431 }
432 }
433 }
434 elseif ($contribution->_component == 'contribute') {
435 $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id);
436
437 $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]);
438 $sendTemplateParams['from'] = $fromEmailAddress;
439 $sendTemplateParams['toEmail'] = $email;
440 $sendTemplateParams['cc'] = $values['cc_receipt'] ?? NULL;
441 $sendTemplateParams['bcc'] = $values['bcc_receipt'] ?? NULL;
442
443 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
444 // functions call for adding activity with attachment
445 $fileName = self::putFile($html, $pdfFileName);
446 self::addActivities($subject, $contribution->contact_id, $fileName, $params, $contribution->id);
447 }
448 elseif ($contribution->_component == 'event') {
449 $email = CRM_Contact_BAO_Contact::getPrimaryEmail($contribution->contact_id);
450
451 $sendTemplateParams['tplParams'] = array_merge($tplParams, ['email_comment' => $invoiceElements['params']['email_comment']]);
452 $sendTemplateParams['from'] = $fromEmailAddress;
453 $sendTemplateParams['toEmail'] = $email;
454 $sendTemplateParams['cc'] = $values['cc_confirm'] ?? NULL;
455 $sendTemplateParams['bcc'] = $values['bcc_confirm'] ?? NULL;
456
457 list($sent, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate($sendTemplateParams);
458 // functions call for adding activity with attachment
459 $fileName = self::putFile($html, $pdfFileName);
460 self::addActivities($subject, $contribution->contact_id, $fileName, $params, $contribution->id);
461 }
462 $invoiceTemplate->clearTemplateVars();
463 }
464
465 if ($invoiceElements['createPdf']) {
466 if (isset($params['forPage'])) {
467 return $html;
468 }
469 else {
470 CRM_Utils_PDF_Utils::html2pdf($messageInvoice, $pdfFileName, FALSE, [
471 'margin_top' => 10,
472 'margin_left' => 65,
473 'metric' => 'px',
474 ]);
475 // functions call for adding activity with attachment
476 $fileName = self::putFile($html, $pdfFileName, [
477 'margin_top' => 10,
478 'margin_left' => 65,
479 'metric' => 'px',
480 ]);
481 self::addActivities($subject, $contactIds, $fileName, $params);
482
483 CRM_Utils_System::civiExit();
484 }
485 }
486 else {
487 if ($invoiceElements['suppressedEmails']) {
488 $status = ts('Email was NOT sent to %1 contacts (no email address on file, or communication preferences specify DO NOT EMAIL, or contact is deceased).', [1 => $invoiceElements['suppressedEmails']]);
489 $msgTitle = ts('Email Error');
490 $msgType = 'error';
491 }
492 else {
493 $status = ts('Your mail has been sent.');
494 $msgTitle = ts('Sent');
495 $msgType = 'success';
496 }
497 CRM_Core_Session::setStatus($status, $msgTitle, $msgType);
498 }
499 }
500
501 /**
502 * Add activity for Email Invoice and the PDF Invoice.
503 *
504 * @param string $subject
505 * Activity subject.
506 * @param array $contactIds
507 * Contact Id.
508 * @param string $fileName
509 * Gives the location with name of the file.
510 * @param array $params
511 * For invoices.
512 * @param int $contributionId
513 * Contribution Id.
514 *
515 */
516 public static function addActivities($subject, $contactIds, $fileName, $params, $contributionId = NULL) {
517 $session = CRM_Core_Session::singleton();
518 $userID = $session->get('userID');
519 $config = CRM_Core_Config::singleton();
520 $config->doNotAttachPDFReceipt = 1;
521
522 if (!empty($params['output']) && $params['output'] == 'pdf_invoice') {
523 $activityType = 'Downloaded Invoice';
524 }
525 else {
526 $activityType = 'Emailed Invoice';
527 }
528
529 $activityParams = [
530 'subject' => $subject,
531 'source_contact_id' => $userID,
532 'target_contact_id' => $contactIds,
533 'activity_type_id' => $activityType,
534 'activity_date_time' => date('YmdHis'),
535 'attachFile_1' => [
536 'uri' => $fileName,
537 'type' => 'application/pdf',
538 'location' => $fileName,
539 'upload_date' => date('YmdHis'),
540 ],
541 ];
542 if ($contributionId) {
543 $activityParams['source_record_id'] = $contributionId;
544 }
545 civicrm_api3('Activity', 'create', $activityParams);
546 }
547
548 /**
549 * Create the Invoice file in upload folder for attachment.
550 *
551 * @param string $html
552 * Content for pdf in html format.
553 *
554 * @param string $name
555 * @param array $format
556 *
557 * @return string
558 * Name of file which is in pdf format
559 */
560 public static function putFile($html, $name = 'Invoice.pdf', $format = NULL) {
561 return CRM_Utils_Mail::appendPDF($name, $html, $format)['fullPath'] ?? '';
562 }
563
564 /**
565 * Callback to perform action on Print Invoice button.
566 */
567 public static function getPrintPDF() {
568 $contributionId = CRM_Utils_Request::retrieve('id', 'Positive', CRM_Core_DAO::$_nullObject, FALSE);
569 $contributionIDs = [$contributionId];
570 $contactId = CRM_Utils_Request::retrieve('cid', 'Positive', CRM_Core_DAO::$_nullObject, FALSE);
571 $params = ['output' => 'pdf_invoice'];
572 CRM_Contribute_Form_Task_Invoice::printPDF($contributionIDs, $params, $contactId);
573 }
574
575 }