1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9package Kernel::System::Stats::Dynamic::TicketList; 10 11use strict; 12use warnings; 13 14use List::Util qw( first ); 15 16use Kernel::System::VariableCheck qw(:all); 17use Kernel::Language qw(Translatable); 18 19our @ObjectDependencies = ( 20 'Kernel::Config', 21 'Kernel::Language', 22 'Kernel::System::DB', 23 'Kernel::System::DynamicField', 24 'Kernel::System::DynamicField::Backend', 25 'Kernel::System::Lock', 26 'Kernel::System::Log', 27 'Kernel::System::Priority', 28 'Kernel::System::Queue', 29 'Kernel::System::Service', 30 'Kernel::System::SLA', 31 'Kernel::System::State', 32 'Kernel::System::Stats', 33 'Kernel::System::Ticket', 34 'Kernel::System::Ticket::Article', 35 'Kernel::System::DateTime', 36 'Kernel::System::Type', 37 'Kernel::System::User', 38 'Kernel::Output::HTML::Layout', 39); 40 41sub new { 42 my ( $Type, %Param ) = @_; 43 44 # allocate new hash for object 45 my $Self = {}; 46 bless( $Self, $Type ); 47 48 # get the dynamic fields for ticket object 49 $Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( 50 Valid => 1, 51 ObjectType => ['Ticket'], 52 ); 53 54 return $Self; 55} 56 57sub GetObjectName { 58 my ( $Self, %Param ) = @_; 59 60 return 'Ticketlist'; 61} 62 63sub GetObjectBehaviours { 64 my ( $Self, %Param ) = @_; 65 66 my %Behaviours = ( 67 ProvidesDashboardWidget => 0, 68 ); 69 70 return %Behaviours; 71} 72 73sub GetObjectAttributes { 74 my ( $Self, %Param ) = @_; 75 76 # get needed objects 77 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 78 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 79 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 80 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 81 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 82 my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); 83 my $LockObject = $Kernel::OM->Get('Kernel::System::Lock'); 84 85 my $ValidAgent = 0; 86 if ( 87 defined $ConfigObject->Get('Stats::UseInvalidAgentInStats') 88 && ( $ConfigObject->Get('Stats::UseInvalidAgentInStats') == 0 ) 89 ) 90 { 91 $ValidAgent = 1; 92 } 93 94 # Get user list without the out of office message, because of the caching in the statistics 95 # and not meaningful with a date selection. 96 my %UserList = $UserObject->UserList( 97 Type => 'Long', 98 Valid => $ValidAgent, 99 NoOutOfOffice => 1, 100 ); 101 102 # get state list 103 my %StateList = $StateObject->StateList( 104 UserID => 1, 105 ); 106 107 # get state type list 108 my %StateTypeList = $StateObject->StateTypeList( 109 UserID => 1, 110 ); 111 112 # get queue list 113 my %QueueList = $QueueObject->GetAllQueues(); 114 115 # get priority list 116 my %PriorityList = $PriorityObject->PriorityList( 117 UserID => 1, 118 ); 119 120 # get lock list 121 my %LockList = $LockObject->LockList( 122 UserID => 1, 123 ); 124 125 my %Limit = ( 126 5 => 5, 127 10 => 10, 128 20 => 20, 129 50 => 50, 130 100 => 100, 131 unlimited => Translatable('unlimited'), 132 ); 133 134 my %TicketAttributes = %{ $Self->_TicketAttributes() }; 135 my %OrderBy = map { $_ => $TicketAttributes{$_} } grep { $_ ne 'Number' } keys %TicketAttributes; 136 137 # get dynamic field backend object 138 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 139 140 # remove non sortable (and orderable) Dynamic Fields 141 DYNAMICFIELD: 142 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 143 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 144 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 145 146 # check if dynamic field is sortable 147 my $IsSortable = $DynamicFieldBackendObject->HasBehavior( 148 DynamicFieldConfig => $DynamicFieldConfig, 149 Behavior => 'IsSortable', 150 ); 151 152 # remove dynamic fields from the list if is not sortable 153 if ( !$IsSortable ) { 154 delete $OrderBy{ 'DynamicField_' . $DynamicFieldConfig->{Name} }; 155 } 156 } 157 158 my %SortSequence = ( 159 Up => Translatable('ascending'), 160 Down => Translatable('descending'), 161 ); 162 163 my @ObjectAttributes = ( 164 { 165 Name => Translatable('Attributes to be printed'), 166 UseAsXvalue => 1, 167 UseAsValueSeries => 0, 168 UseAsRestriction => 0, 169 Element => 'TicketAttributes', 170 Block => 'MultiSelectField', 171 Translation => 1, 172 Values => \%TicketAttributes, 173 Sort => 'IndividualKey', 174 SortIndividual => $Self->_SortedAttributes(), 175 }, 176 { 177 Name => Translatable('Order by'), 178 UseAsXvalue => 0, 179 UseAsValueSeries => 1, 180 UseAsRestriction => 0, 181 Element => 'OrderBy', 182 Block => 'SelectField', 183 Translation => 1, 184 Values => \%OrderBy, 185 Sort => 'IndividualKey', 186 SortIndividual => $Self->_SortedAttributes(), 187 }, 188 { 189 Name => Translatable('Sort sequence'), 190 UseAsXvalue => 0, 191 UseAsValueSeries => 1, 192 UseAsRestriction => 0, 193 Element => 'SortSequence', 194 Block => 'SelectField', 195 Translation => 1, 196 Values => \%SortSequence, 197 }, 198 { 199 Name => Translatable('Limit'), 200 UseAsXvalue => 0, 201 UseAsValueSeries => 0, 202 UseAsRestriction => 1, 203 Element => 'Limit', 204 Block => 'SelectField', 205 Translation => 1, 206 Values => \%Limit, 207 Sort => 'IndividualKey', 208 SortIndividual => [ '5', '10', '20', '50', '100', 'unlimited', ], 209 }, 210 { 211 Name => Translatable('Queue'), 212 UseAsXvalue => 0, 213 UseAsValueSeries => 0, 214 UseAsRestriction => 1, 215 Element => 'QueueIDs', 216 Block => 'MultiSelectField', 217 Translation => 0, 218 TreeView => 1, 219 Values => \%QueueList, 220 }, 221 { 222 Name => Translatable('State'), 223 UseAsXvalue => 0, 224 UseAsValueSeries => 0, 225 UseAsRestriction => 1, 226 Element => 'StateIDs', 227 Block => 'MultiSelectField', 228 Values => \%StateList, 229 }, 230 { 231 Name => Translatable('State Historic'), 232 UseAsXvalue => 0, 233 UseAsValueSeries => 0, 234 UseAsRestriction => 1, 235 Element => 'StateIDsHistoric', 236 Block => 'MultiSelectField', 237 Values => \%StateList, 238 }, 239 { 240 Name => Translatable('State Type'), 241 UseAsXvalue => 0, 242 UseAsValueSeries => 0, 243 UseAsRestriction => 1, 244 Element => 'StateTypeIDs', 245 Block => 'MultiSelectField', 246 Values => \%StateTypeList, 247 }, 248 { 249 Name => Translatable('State Type Historic'), 250 UseAsXvalue => 0, 251 UseAsValueSeries => 0, 252 UseAsRestriction => 1, 253 Element => 'StateTypeIDsHistoric', 254 Block => 'MultiSelectField', 255 Values => \%StateTypeList, 256 }, 257 { 258 Name => Translatable('Priority'), 259 UseAsXvalue => 0, 260 UseAsValueSeries => 0, 261 UseAsRestriction => 1, 262 Element => 'PriorityIDs', 263 Block => 'MultiSelectField', 264 Values => \%PriorityList, 265 }, 266 { 267 Name => Translatable('Created in Queue'), 268 UseAsXvalue => 0, 269 UseAsValueSeries => 0, 270 UseAsRestriction => 1, 271 Element => 'CreatedQueueIDs', 272 Block => 'MultiSelectField', 273 Translation => 0, 274 TreeView => 1, 275 Values => \%QueueList, 276 }, 277 { 278 Name => Translatable('Created Priority'), 279 UseAsXvalue => 0, 280 UseAsValueSeries => 0, 281 UseAsRestriction => 1, 282 Element => 'CreatedPriorityIDs', 283 Block => 'MultiSelectField', 284 Values => \%PriorityList, 285 }, 286 { 287 Name => Translatable('Created State'), 288 UseAsXvalue => 0, 289 UseAsValueSeries => 0, 290 UseAsRestriction => 1, 291 Element => 'CreatedStateIDs', 292 Block => 'MultiSelectField', 293 Values => \%StateList, 294 }, 295 { 296 Name => Translatable('Lock'), 297 UseAsXvalue => 0, 298 UseAsValueSeries => 0, 299 UseAsRestriction => 1, 300 Element => 'LockIDs', 301 Block => 'MultiSelectField', 302 Values => \%LockList, 303 }, 304 { 305 Name => Translatable('Title'), 306 UseAsXvalue => 0, 307 UseAsValueSeries => 0, 308 UseAsRestriction => 1, 309 Element => 'Title', 310 Block => 'InputField', 311 }, 312 { 313 Name => Translatable('From'), 314 UseAsXvalue => 0, 315 UseAsValueSeries => 0, 316 UseAsRestriction => 1, 317 Element => 'MIMEBase_From', 318 Block => 'InputField', 319 }, 320 { 321 Name => Translatable('To'), 322 UseAsXvalue => 0, 323 UseAsValueSeries => 0, 324 UseAsRestriction => 1, 325 Element => 'MIMEBase_To', 326 Block => 'InputField', 327 }, 328 { 329 Name => Translatable('Cc'), 330 UseAsXvalue => 0, 331 UseAsValueSeries => 0, 332 UseAsRestriction => 1, 333 Element => 'MIMEBase_Cc', 334 Block => 'InputField', 335 }, 336 { 337 Name => Translatable('Subject'), 338 UseAsXvalue => 0, 339 UseAsValueSeries => 0, 340 UseAsRestriction => 1, 341 Element => 'MIMEBase_Subject', 342 Block => 'InputField', 343 }, 344 { 345 Name => Translatable('Text'), 346 UseAsXvalue => 0, 347 UseAsValueSeries => 0, 348 UseAsRestriction => 1, 349 Element => 'MIMEBase_Body', 350 Block => 'InputField', 351 }, 352 { 353 Name => Translatable('Create Time'), 354 UseAsXvalue => 0, 355 UseAsValueSeries => 0, 356 UseAsRestriction => 1, 357 Element => 'CreateTime', 358 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 359 Block => 'Time', 360 Values => { 361 TimeStart => 'TicketCreateTimeNewerDate', 362 TimeStop => 'TicketCreateTimeOlderDate', 363 }, 364 }, 365 { 366 Name => Translatable('Last changed times'), 367 UseAsXvalue => 0, 368 UseAsValueSeries => 0, 369 UseAsRestriction => 1, 370 Element => 'LastChangeTime', 371 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 372 Block => 'Time', 373 Values => { 374 TimeStart => 'TicketLastChangeTimeNewerDate', 375 TimeStop => 'TicketLastChangeTimeOlderDate', 376 }, 377 }, 378 { 379 Name => Translatable('Change times'), 380 UseAsXvalue => 0, 381 UseAsValueSeries => 0, 382 UseAsRestriction => 1, 383 Element => 'ChangeTime', 384 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 385 Block => 'Time', 386 Values => { 387 TimeStart => 'TicketChangeTimeNewerDate', 388 TimeStop => 'TicketChangeTimeOlderDate', 389 }, 390 }, 391 { 392 Name => Translatable('Pending until time'), 393 UseAsXvalue => 0, 394 UseAsValueSeries => 0, 395 UseAsRestriction => 1, 396 Element => 'PendingUntilTime', 397 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 398 Block => 'Time', 399 Values => { 400 TimeStart => 'TicketPendingTimeNewerDate', 401 TimeStop => 'TicketPendingTimeOlderDate', 402 }, 403 }, 404 { 405 Name => Translatable('Close Time'), 406 UseAsXvalue => 0, 407 UseAsValueSeries => 0, 408 UseAsRestriction => 1, 409 Element => 'CloseTime', 410 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 411 Block => 'Time', 412 Values => { 413 TimeStart => 'TicketCloseTimeNewerDate', 414 TimeStop => 'TicketCloseTimeOlderDate', 415 }, 416 }, 417 { 418 Name => Translatable('Historic Time Range'), 419 UseAsXvalue => 0, 420 UseAsValueSeries => 0, 421 UseAsRestriction => 1, 422 Element => 'HistoricTimeRange', 423 TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong', 424 Block => 'Time', 425 Values => { 426 TimeStart => 'HistoricTimeRangeTimeNewerDate', 427 TimeStop => 'HistoricTimeRangeTimeOlderDate', 428 }, 429 }, 430 { 431 Name => Translatable('Escalation'), 432 UseAsXvalue => 0, 433 UseAsValueSeries => 0, 434 UseAsRestriction => 1, 435 Element => 'EscalationTime', 436 TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat', 437 Block => 'Time', 438 Values => { 439 TimeStart => 'TicketEscalationTimeNewerDate', 440 TimeStop => 'TicketEscalationTimeOlderDate', 441 }, 442 }, 443 { 444 Name => Translatable('Escalation - First Response Time'), 445 UseAsXvalue => 0, 446 UseAsValueSeries => 0, 447 UseAsRestriction => 1, 448 Element => 'EscalationResponseTime', 449 TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat', 450 Block => 'Time', 451 Values => { 452 TimeStart => 'TicketEscalationResponseTimeNewerDate', 453 TimeStop => 'TicketEscalationResponseTimeOlderDate', 454 }, 455 }, 456 { 457 Name => Translatable('Escalation - Update Time'), 458 UseAsXvalue => 0, 459 UseAsValueSeries => 0, 460 UseAsRestriction => 1, 461 Element => 'EscalationUpdateTime', 462 TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat', 463 Block => 'Time', 464 Values => { 465 TimeStart => 'TicketEscalationUpdateTimeNewerDate', 466 TimeStop => 'TicketEscalationUpdateTimeOlderDate', 467 }, 468 }, 469 { 470 Name => Translatable('Escalation - Solution Time'), 471 UseAsXvalue => 0, 472 UseAsValueSeries => 0, 473 UseAsRestriction => 1, 474 Element => 'EscalationSolutionTime', 475 TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat', 476 Block => 'Time', 477 Values => { 478 TimeStart => 'TicketEscalationSolutionTimeNewerDate', 479 TimeStop => 'TicketEscalationSolutionTimeOlderDate', 480 }, 481 }, 482 ); 483 484 if ( $ConfigObject->Get('Ticket::Service') ) { 485 486 # get service list 487 my %Service = $Kernel::OM->Get('Kernel::System::Service')->ServiceList( 488 KeepChildren => $ConfigObject->Get('Ticket::Service::KeepChildren'), 489 UserID => 1, 490 ); 491 492 # get sla list 493 my %SLA = $Kernel::OM->Get('Kernel::System::SLA')->SLAList( 494 UserID => 1, 495 ); 496 497 my @ObjectAttributeAdd = ( 498 { 499 Name => Translatable('Service'), 500 UseAsXvalue => 0, 501 UseAsValueSeries => 0, 502 UseAsRestriction => 1, 503 Element => 'ServiceIDs', 504 Block => 'MultiSelectField', 505 Translation => 0, 506 TreeView => 1, 507 Values => \%Service, 508 }, 509 { 510 Name => Translatable('SLA'), 511 UseAsXvalue => 0, 512 UseAsValueSeries => 0, 513 UseAsRestriction => 1, 514 Element => 'SLAIDs', 515 Block => 'MultiSelectField', 516 Translation => 0, 517 Values => \%SLA, 518 }, 519 ); 520 521 unshift @ObjectAttributes, @ObjectAttributeAdd; 522 } 523 524 if ( $ConfigObject->Get('Ticket::Type') ) { 525 526 # get ticket type list 527 my %Type = $Kernel::OM->Get('Kernel::System::Type')->TypeList( 528 UserID => 1, 529 ); 530 531 my %ObjectAttribute1 = ( 532 Name => Translatable('Type'), 533 UseAsXvalue => 0, 534 UseAsValueSeries => 0, 535 UseAsRestriction => 1, 536 Element => 'TypeIDs', 537 Block => 'MultiSelectField', 538 Translation => 0, 539 Values => \%Type, 540 ); 541 542 unshift @ObjectAttributes, \%ObjectAttribute1; 543 } 544 545 if ( $ConfigObject->Get('Stats::UseAgentElementInStats') ) { 546 547 my @ObjectAttributeAdd = ( 548 { 549 Name => Translatable('Agent/Owner'), 550 UseAsXvalue => 0, 551 UseAsValueSeries => 0, 552 UseAsRestriction => 1, 553 Element => 'OwnerIDs', 554 Block => 'MultiSelectField', 555 Translation => 0, 556 Values => \%UserList, 557 }, 558 { 559 Name => Translatable('Created by Agent/Owner'), 560 UseAsXvalue => 0, 561 UseAsValueSeries => 0, 562 UseAsRestriction => 1, 563 Element => 'CreatedUserIDs', 564 Block => 'MultiSelectField', 565 Translation => 0, 566 Values => \%UserList, 567 }, 568 { 569 Name => Translatable('Responsible'), 570 UseAsXvalue => 0, 571 UseAsValueSeries => 0, 572 UseAsRestriction => 1, 573 Element => 'ResponsibleIDs', 574 Block => 'MultiSelectField', 575 Translation => 0, 576 Values => \%UserList, 577 }, 578 ); 579 580 push @ObjectAttributes, @ObjectAttributeAdd; 581 } 582 583 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 584 585 if ( $ConfigObject->Get('Stats::CustomerIDAsMultiSelect') ) { 586 587 # Get CustomerID 588 # (This way also can be the solution for the CustomerUserID) 589 $DBObject->Prepare( 590 SQL => "SELECT DISTINCT customer_id FROM ticket", 591 ); 592 593 # fetch the result 594 my %CustomerID; 595 while ( my @Row = $DBObject->FetchrowArray() ) { 596 if ( $Row[0] ) { 597 $CustomerID{ $Row[0] } = $Row[0]; 598 } 599 } 600 601 my %ObjectAttribute = ( 602 Name => Translatable('Customer ID'), 603 UseAsXvalue => 0, 604 UseAsValueSeries => 0, 605 UseAsRestriction => 1, 606 Element => 'CustomerID', 607 Block => 'MultiSelectField', 608 Values => \%CustomerID, 609 ); 610 611 push @ObjectAttributes, \%ObjectAttribute; 612 } 613 else { 614 615 my @CustomerIDAttributes = ( 616 { 617 Name => Translatable('CustomerID (complex search)'), 618 UseAsXvalue => 0, 619 UseAsValueSeries => 0, 620 UseAsRestriction => 1, 621 Element => 'CustomerID', 622 Block => 'InputField', 623 }, 624 { 625 Name => Translatable('CustomerID (exact match)'), 626 UseAsXvalue => 0, 627 UseAsValueSeries => 0, 628 UseAsRestriction => 1, 629 Element => 'CustomerIDRaw', 630 Block => 'InputField', 631 }, 632 ); 633 634 push @ObjectAttributes, @CustomerIDAttributes; 635 } 636 637 if ( $ConfigObject->Get('Stats::CustomerUserLoginsAsMultiSelect') ) { 638 639 # Get all CustomerUserLogins which are related to a tiket. 640 $DBObject->Prepare( 641 SQL => "SELECT DISTINCT customer_user_id FROM ticket", 642 ); 643 644 # fetch the result 645 my %CustomerUserIDs; 646 while ( my @Row = $DBObject->FetchrowArray() ) { 647 if ( $Row[0] ) { 648 $CustomerUserIDs{ $Row[0] } = $Row[0]; 649 } 650 } 651 652 my %ObjectAttribute = ( 653 Name => Translatable('Assigned to Customer User Login'), 654 UseAsXvalue => 0, 655 UseAsValueSeries => 0, 656 UseAsRestriction => 1, 657 Element => 'CustomerUserLoginRaw', 658 Block => 'MultiSelectField', 659 Values => \%CustomerUserIDs, 660 ); 661 662 push @ObjectAttributes, \%ObjectAttribute; 663 } 664 else { 665 666 my @CustomerUserAttributes = ( 667 { 668 Name => Translatable('Assigned to Customer User Login (complex search)'), 669 UseAsXvalue => 0, 670 UseAsValueSeries => 0, 671 UseAsRestriction => 1, 672 Element => 'CustomerUserLogin', 673 Block => 'InputField', 674 }, 675 { 676 Name => Translatable('Assigned to Customer User Login (exact match)'), 677 UseAsXvalue => 0, 678 UseAsValueSeries => 0, 679 UseAsRestriction => 1, 680 Element => 'CustomerUserLoginRaw', 681 Block => 'InputField', 682 CSSClass => 'CustomerAutoCompleteSimple', 683 HTMLDataAttributes => { 684 'customer-search-type' => 'CustomerUser', 685 }, 686 }, 687 ); 688 689 push @ObjectAttributes, @CustomerUserAttributes; 690 } 691 692 # Add always the field for the customer user login accessible tickets as auto complete field. 693 my %ObjectAttribute = ( 694 Name => Translatable('Accessible to Customer User Login (exact match)'), 695 UseAsXvalue => 0, 696 UseAsValueSeries => 0, 697 UseAsRestriction => 1, 698 Element => 'CustomerUserID', 699 Block => 'InputField', 700 CSSClass => 'CustomerAutoCompleteSimple', 701 HTMLDataAttributes => { 702 'customer-search-type' => 'CustomerUser', 703 }, 704 ); 705 push @ObjectAttributes, \%ObjectAttribute; 706 707 if ( $ConfigObject->Get('Ticket::ArchiveSystem') ) { 708 709 my %ObjectAttribute = ( 710 Name => Translatable('Archive Search'), 711 UseAsXvalue => 0, 712 UseAsValueSeries => 0, 713 UseAsRestriction => 1, 714 Element => 'SearchInArchive', 715 Block => 'SelectField', 716 Translation => 1, 717 Values => { 718 ArchivedTickets => Translatable('Archived tickets'), 719 NotArchivedTickets => Translatable('Unarchived tickets'), 720 AllTickets => Translatable('All tickets'), 721 }, 722 ); 723 724 push @ObjectAttributes, \%ObjectAttribute; 725 } 726 727 # cycle trough the activated Dynamic Fields for this screen 728 DYNAMICFIELD: 729 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 730 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 731 732 # skip all fields not designed to be supported by statistics 733 my $IsStatsCondition = $DynamicFieldBackendObject->HasBehavior( 734 DynamicFieldConfig => $DynamicFieldConfig, 735 Behavior => 'IsStatsCondition', 736 ); 737 738 next DYNAMICFIELD if !$IsStatsCondition; 739 740 my $PossibleValuesFilter; 741 742 my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior( 743 DynamicFieldConfig => $DynamicFieldConfig, 744 Behavior => 'IsACLReducible', 745 ); 746 747 if ($IsACLReducible) { 748 749 # get PossibleValues 750 my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet( 751 DynamicFieldConfig => $DynamicFieldConfig, 752 ); 753 754 # convert possible values key => value to key => key for ACLs using a Hash slice 755 my %AclData = %{ $PossibleValues || {} }; 756 @AclData{ keys %AclData } = keys %AclData; 757 758 # set possible values filter from ACLs 759 my $ACL = $TicketObject->TicketAcl( 760 Action => 'AgentStats', 761 Type => 'DynamicField_' . $DynamicFieldConfig->{Name}, 762 ReturnType => 'Ticket', 763 ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name}, 764 Data => \%AclData || {}, 765 UserID => 1, 766 ); 767 if ($ACL) { 768 my %Filter = $TicketObject->TicketAclData(); 769 770 # convert Filer key => key back to key => value using map 771 %{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } keys %Filter; 772 } 773 } 774 775 # get dynamic field stats parameters 776 my $DynamicFieldStatsParameter = $DynamicFieldBackendObject->StatsFieldParameterBuild( 777 DynamicFieldConfig => $DynamicFieldConfig, 778 PossibleValuesFilter => $PossibleValuesFilter, 779 ); 780 781 if ( IsHashRefWithData($DynamicFieldStatsParameter) ) { 782 783 # backward compatibility 784 if ( !$DynamicFieldStatsParameter->{Block} ) { 785 $DynamicFieldStatsParameter->{Block} = 'InputField'; 786 if ( IsHashRefWithData( $DynamicFieldStatsParameter->{Values} ) ) { 787 $DynamicFieldStatsParameter->{Block} = 'MultiSelectField'; 788 } 789 } 790 791 if ( $DynamicFieldStatsParameter->{Block} eq 'Time' ) { 792 793 # create object attributes (date/time fields) 794 my $TimePeriodFormat = $DynamicFieldStatsParameter->{TimePeriodFormat} || 'DateInputFormatLong'; 795 796 my %ObjectAttribute = ( 797 Name => $DynamicFieldStatsParameter->{Name}, 798 UseAsXvalue => 0, 799 UseAsValueSeries => 0, 800 UseAsRestriction => 1, 801 Element => $DynamicFieldStatsParameter->{Element}, 802 TimePeriodFormat => $TimePeriodFormat, 803 Block => $DynamicFieldStatsParameter->{Block}, 804 TimePeriodFormat => $TimePeriodFormat, 805 Values => { 806 TimeStart => 807 $DynamicFieldStatsParameter->{Element} 808 . '_GreaterThanEquals', 809 TimeStop => 810 $DynamicFieldStatsParameter->{Element} 811 . '_SmallerThanEquals', 812 }, 813 ); 814 push @ObjectAttributes, \%ObjectAttribute; 815 } 816 elsif ( $DynamicFieldStatsParameter->{Block} eq 'MultiSelectField' ) { 817 818 # create object attributes (multiple values) 819 my %ObjectAttribute = ( 820 Name => $DynamicFieldStatsParameter->{Name}, 821 UseAsXvalue => 0, 822 UseAsValueSeries => 0, 823 UseAsRestriction => 1, 824 Element => $DynamicFieldStatsParameter->{Element}, 825 Block => $DynamicFieldStatsParameter->{Block}, 826 Values => $DynamicFieldStatsParameter->{Values}, 827 Translation => $DynamicFieldStatsParameter->{TranslatableValues} || 0, 828 IsDynamicField => 1, 829 ShowAsTree => $DynamicFieldConfig->{Config}->{TreeView} || 0, 830 ); 831 push @ObjectAttributes, \%ObjectAttribute; 832 } 833 else { 834 835 # create object attributes (text fields) 836 my %ObjectAttribute = ( 837 Name => $DynamicFieldStatsParameter->{Name}, 838 UseAsXvalue => 0, 839 UseAsValueSeries => 0, 840 UseAsRestriction => 1, 841 Element => $DynamicFieldStatsParameter->{Element}, 842 Block => $DynamicFieldStatsParameter->{Block}, 843 ); 844 push @ObjectAttributes, \%ObjectAttribute; 845 } 846 } 847 } 848 849 return @ObjectAttributes; 850} 851 852sub GetStatTablePreview { 853 my ( $Self, %Param ) = @_; 854 855 return $Self->GetStatTable( 856 %Param, 857 Preview => 1, 858 ); 859} 860 861sub GetStatTable { 862 my ( $Self, %Param ) = @_; 863 my %TicketAttributes = map { $_ => 1 } @{ $Param{XValue}{SelectedValues} }; 864 my $SortedAttributesRef = $Self->_SortedAttributes(); 865 my $Preview = $Param{Preview}; 866 867 my $DBObject = $Kernel::OM->Get('Kernel::System::DB'); 868 869 # check if a enumeration is requested 870 my $AddEnumeration = 0; 871 if ( $TicketAttributes{Number} ) { 872 $AddEnumeration = 1; 873 delete $TicketAttributes{Number}; 874 } 875 876 # set default values if no sort or order attribute is given 877 my $OrderRef = first { $_->{Element} eq 'OrderBy' } @{ $Param{ValueSeries} }; 878 my $OrderBy = $OrderRef ? $OrderRef->{SelectedValues}[0] : 'Age'; 879 my $SortRef = first { $_->{Element} eq 'SortSequence' } @{ $Param{ValueSeries} }; 880 my $Sort = $SortRef ? $SortRef->{SelectedValues}[0] : 'Down'; 881 my $Limit = $Param{Restrictions}{Limit}; 882 883 # check if we can use the sort and order function of TicketSearch 884 my $OrderByIsValueOfTicketSearchSort = $Self->_OrderByIsValueOfTicketSearchSort( 885 OrderBy => $OrderBy, 886 ); 887 888 # escape search attributes for ticket search 889 my %AttributesToEscape = ( 890 'CustomerID' => 1, 891 'Title' => 1, 892 ); 893 894 # Map the CustomerID search parameter to CustomerIDRaw search parameter for the 895 # exact search match, if the 'Stats::CustomerIDAsMultiSelect' is active. 896 if ( $Kernel::OM->Get('Kernel::Config')->Get('Stats::CustomerIDAsMultiSelect') ) { 897 $Param{Restrictions}->{CustomerIDRaw} = $Param{Restrictions}->{CustomerID}; 898 } 899 900 ATTRIBUTE: 901 for my $Key ( sort keys %{ $Param{Restrictions} } ) { 902 903 next ATTRIBUTE if !$AttributesToEscape{$Key}; 904 905 if ( ref $Param{Restrictions}->{$Key} ) { 906 if ( ref $Param{Restrictions}->{$Key} eq 'ARRAY' ) { 907 $Param{Restrictions}->{$Key} = [ 908 map { $DBObject->QueryStringEscape( QueryString => $_ ) } 909 @{ $Param{Restrictions}->{$Key} } 910 ]; 911 } 912 } 913 else { 914 $Param{Restrictions}->{$Key} = $DBObject->QueryStringEscape( 915 QueryString => $Param{Restrictions}->{$Key} 916 ); 917 } 918 } 919 920 # get dynamic field backend object 921 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 922 923 my %DynamicFieldRestrictions; 924 for my $ParameterName ( sort keys %{ $Param{Restrictions} } ) { 925 if ( 926 $ParameterName =~ m{ \A DynamicField_ ( [a-zA-Z\d]+ ) (?: _ ( [a-zA-Z\d]+ ) )? \z }xms 927 ) 928 { 929 my $FieldName = $1; 930 my $Operator = $2; 931 932 # loop over the dynamic fields configured 933 DYNAMICFIELD: 934 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 935 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 936 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 937 938 # skip all fields that do not match with current field name 939 # without the 'DynamicField_' prefix 940 next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $FieldName; 941 942 # skip all fields not designed to be supported by statistics 943 my $IsStatsCondition = $DynamicFieldBackendObject->HasBehavior( 944 DynamicFieldConfig => $DynamicFieldConfig, 945 Behavior => 'IsStatsCondition', 946 ); 947 948 next DYNAMICFIELD if !$IsStatsCondition; 949 950 # get new search parameter 951 my $DynamicFieldStatsSearchParameter = $DynamicFieldBackendObject->StatsSearchFieldParameterBuild( 952 DynamicFieldConfig => $DynamicFieldConfig, 953 Value => $Param{Restrictions}->{$ParameterName}, 954 Operator => $Operator, 955 ); 956 957 # add new search parameter 958 if ( !IsHashRefWithData( $DynamicFieldRestrictions{"DynamicField_$FieldName"} ) ) { 959 $DynamicFieldRestrictions{"DynamicField_$FieldName"} = 960 $DynamicFieldStatsSearchParameter; 961 } 962 963 # extend search parameter 964 elsif ( IsHashRefWithData($DynamicFieldStatsSearchParameter) ) { 965 $DynamicFieldRestrictions{"DynamicField_$FieldName"} = { 966 %{ $DynamicFieldRestrictions{"DynamicField_$FieldName"} }, 967 %{$DynamicFieldStatsSearchParameter}, 968 }; 969 } 970 } 971 } 972 } 973 974 if ($OrderByIsValueOfTicketSearchSort) { 975 976 # don't be irritated of the mixture OrderBy <> Sort and SortBy <> OrderBy 977 # the meaning is in TicketSearch is different as in common handling 978 $Param{Restrictions}{OrderBy} = $Sort; 979 $Param{Restrictions}{SortBy} = $OrderByIsValueOfTicketSearchSort; 980 $Param{Restrictions}{Limit} = !$Limit || $Limit eq 'unlimited' ? 100_000_000 : $Limit; 981 } 982 else { 983 $Param{Restrictions}{Limit} = 100_000_000; 984 } 985 986 # get state object 987 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 988 989 # OlderTicketsExclude for historic searches 990 # takes tickets that were closed before the 991 # start of the searched time periode 992 my %OlderTicketsExclude; 993 994 # NewerTicketExclude for historic searches 995 # takes tickets that were created after the 996 # searched time periode 997 my %NewerTicketsExclude; 998 my %StateList = $StateObject->StateList( UserID => 1 ); 999 1000 # get time object 1001 my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime'); 1002 1003 # UnixTimeStart & End: 1004 # The Time periode the historic search is executed 1005 # if no time periode has been selected we take 1006 # Unixtime 0 as StartTime and SystemTime as EndTime 1007 my $UnixTimeStart = 0; 1008 my $UnixTimeEnd = $DateTimeObject->ToEpoch(); 1009 1010 if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) { 1011 $Param{Restrictions}->{SearchInArchive} ||= ''; 1012 if ( $Param{Restrictions}->{SearchInArchive} eq 'AllTickets' ) { 1013 $Param{Restrictions}->{ArchiveFlags} = [ 'y', 'n' ]; 1014 } 1015 elsif ( $Param{Restrictions}->{SearchInArchive} eq 'ArchivedTickets' ) { 1016 $Param{Restrictions}->{ArchiveFlags} = ['y']; 1017 } 1018 else { 1019 $Param{Restrictions}->{ArchiveFlags} = ['n']; 1020 } 1021 } 1022 1023 # get ticket object 1024 my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket'); 1025 1026 if ( !$Preview && $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate} ) { 1027 1028 # Find tickets that were closed before the start of our 1029 # HistoricTimeRangeTimeNewerDate, these have to be excluded. 1030 # In order to reduce it quickly we reformat the result array 1031 # to a hash. 1032 my @OldToExclude = $TicketObject->TicketSearch( 1033 UserID => 1, 1034 Result => 'ARRAY', 1035 Permission => 'ro', 1036 TicketCloseTimeOlderDate => $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate}, 1037 ArchiveFlags => $Param{Restrictions}->{ArchiveFlags}, 1038 Limit => 100_000_000, 1039 ); 1040 %OlderTicketsExclude = map { $_ => 1 } @OldToExclude; 1041 1042 $DateTimeObject->Set( String => $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate} ); 1043 $UnixTimeStart = $DateTimeObject->ToEpoch(); 1044 } 1045 if ( !$Preview && $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate} ) { 1046 1047 # Find tickets that were closed after the end of our 1048 # HistoricTimeRangeTimeOlderDate, these have to be excluded 1049 # in order to reduce it quickly we reformat the result array 1050 # to a hash. 1051 my @NewToExclude = $TicketObject->TicketSearch( 1052 UserID => 1, 1053 Result => 'ARRAY', 1054 Permission => 'ro', 1055 TicketCreateTimeNewerDate => $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate}, 1056 ArchiveFlags => $Param{Restrictions}->{ArchiveFlags}, 1057 Limit => 100_000_000, 1058 ); 1059 %NewerTicketsExclude = map { $_ => 1 } @NewToExclude; 1060 1061 $DateTimeObject->Set( String => $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate} ); 1062 $UnixTimeEnd = $DateTimeObject->ToEpoch(); 1063 } 1064 1065 # get the involved tickets 1066 my @TicketIDs; 1067 1068 if ($Preview) { 1069 @TicketIDs = $TicketObject->TicketSearch( 1070 UserID => 1, 1071 Result => 'ARRAY', 1072 Permission => 'ro', 1073 Limit => 10, 1074 ); 1075 } 1076 else { 1077 @TicketIDs = $TicketObject->TicketSearch( 1078 UserID => 1, 1079 Result => 'ARRAY', 1080 Permission => 'ro', 1081 %{ $Param{Restrictions} }, 1082 %DynamicFieldRestrictions, 1083 ); 1084 } 1085 1086 # if we had Tickets we need to reduce the found tickets 1087 # to those not beeing in %OlderTicketsExclude 1088 # as well as not in %NewerTicketsExclude 1089 if ( %OlderTicketsExclude || %NewerTicketsExclude ) { 1090 @TicketIDs = grep { 1091 !defined $OlderTicketsExclude{$_} 1092 && !defined $NewerTicketsExclude{$_} 1093 } @TicketIDs; 1094 } 1095 1096 # if we have to deal with history states 1097 if ( 1098 !$Preview 1099 && ( 1100 $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate} 1101 || $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate} 1102 || ( 1103 defined $Param{Restrictions}->{StateTypeIDsHistoric} 1104 && ref $Param{Restrictions}->{StateTypeIDsHistoric} eq 'ARRAY' 1105 ) 1106 || ( 1107 defined $Param{Restrictions}->{StateIDsHistoric} 1108 && ref $Param{Restrictions}->{StateIDsHistoric} eq 'ARRAY' 1109 ) 1110 ) 1111 && @TicketIDs 1112 ) 1113 { 1114 1115 my $SQLTicketIDInCondition = $DBObject->QueryInCondition( 1116 Key => 'ticket_id', 1117 Values => \@TicketIDs, 1118 QuoteType => 'Integer', 1119 BindMode => 0, 1120 ); 1121 1122 # start building the SQL query from back to front 1123 # what's fixed is the history_type_id we have to search for 1124 # 1 is ticketcreate 1125 # 27 is state update 1126 my $SQL = $SQLTicketIDInCondition . ' AND history_type_id IN (1,27) ORDER BY ticket_id ASC'; 1127 1128 my %StateIDs; 1129 1130 # if we have certain state types we have to search for 1131 # build a hash holding all ticket StateIDs => StateNames 1132 # we are searching for 1133 if ( 1134 defined $Param{Restrictions}->{StateTypeIDsHistoric} 1135 && ref $Param{Restrictions}->{StateTypeIDsHistoric} eq 'ARRAY' 1136 ) 1137 { 1138 1139 # getting the StateListType: 1140 # my %ListType = ( 1141 # 1 => "new", 1142 # 2 => "open", 1143 # 3 => "closed", 1144 # 4 => "pending reminder", 1145 # 5 => "pending auto", 1146 # 6 => "removed", 1147 # 7 => "merged", 1148 # ); 1149 my %ListType = $StateObject->StateTypeList( 1150 UserID => 1, 1151 ); 1152 1153 # Takes the Array of StateTypeID's 1154 # example: (1, 3, 5, 6, 7) 1155 # maps the ID's to the StateTypeNames 1156 # results in a Hash containing the StateTypeNames 1157 # example: 1158 # %StateTypeHash = { 1159 # 'closed' => 1, 1160 # 'removed' => 1, 1161 # 'pending auto' => 1, 1162 # 'merged' => 1, 1163 # 'new' => 1 1164 # }; 1165 my %StateTypeHash = map { $ListType{$_} => 1 } 1166 @{ $Param{Restrictions}->{StateTypeIDsHistoric} }; 1167 1168 # And now get the StatesByType 1169 # Result is a Hash {ID => StateName,} 1170 my @StateTypes = keys %StateTypeHash; 1171 %StateIDs = $StateObject->StateGetStatesByType( 1172 StateType => [ keys %StateTypeHash ], 1173 Result => 'HASH', 1174 ); 1175 } 1176 1177 # if we had certain states selected, add them to the 1178 # StateIDs Hash 1179 if ( 1180 defined $Param{Restrictions}->{StateIDsHistoric} 1181 && ref $Param{Restrictions}->{StateIDsHistoric} eq 'ARRAY' 1182 ) 1183 { 1184 1185 # Validate the StateIDsHistoric list by 1186 # checking if they are in the %StateList hash 1187 # then taking all ValidState ID's and return a hash 1188 # holding { StateID => Name } 1189 my %Tmp = map { $_ => $StateList{$_} } 1190 grep { $StateList{$_} } @{ $Param{Restrictions}->{StateIDsHistoric} }; 1191 %StateIDs = ( %StateIDs, %Tmp ); 1192 } 1193 1194 $SQL = 'SELECT ticket_id, state_id, create_time FROM ticket_history WHERE ' . $SQL; 1195 1196 $DBObject->Prepare( SQL => $SQL ); 1197 1198 # Structure: 1199 # Stores the last TicketState: 1200 # TicketID => [StateID, CreateTime] 1201 my %FoundTickets; 1202 1203 # fetch the result 1204 while ( my @Row = $DBObject->FetchrowArray() ) { 1205 if ( $Row[0] ) { 1206 my $TicketID = $Row[0]; 1207 my $StateID = $Row[1]; 1208 $DateTimeObject->Set( String => $Row[2] ); 1209 my $RowTimeUnix = $DateTimeObject->ToEpoch(); 1210 1211 # Entries before StartTime 1212 if ( $RowTimeUnix < $UnixTimeStart ) { 1213 1214 # if the ticket was already stored 1215 if ( $FoundTickets{$TicketID} ) { 1216 1217 # if the current state is in the searched states 1218 # update the record 1219 if ( $StateIDs{$StateID} ) { 1220 $FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ]; 1221 } 1222 1223 # if it is not in the searched states 1224 # a state change happend -> 1225 # delete the record 1226 else { 1227 delete $FoundTickets{$TicketID}; 1228 } 1229 } 1230 1231 # if the ticket was NOT already stored 1232 # and the state is in the searched states 1233 # store the record 1234 elsif ( $StateIDs{$StateID} ) { 1235 $FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ]; 1236 } 1237 } 1238 1239 # Entries between Start and EndTime 1240 if ( 1241 $RowTimeUnix >= $UnixTimeStart 1242 && $RowTimeUnix <= $UnixTimeEnd 1243 ) 1244 { 1245 1246 # if we found a record 1247 # with the searched states 1248 # add it to the FoundTickets 1249 if ( $StateIDs{$StateID} ) { 1250 $FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ]; 1251 } 1252 } 1253 } 1254 } 1255 1256 # if we had tickets that matched our query 1257 # use them to get the details for the statistic 1258 if (%FoundTickets) { 1259 @TicketIDs = sort { $a <=> $b } keys %FoundTickets; 1260 } 1261 1262 # if no Tickets were remaining, 1263 # after reducing the total amount by the ones 1264 # that had none of the searched states, 1265 # empty @TicketIDs 1266 else { 1267 @TicketIDs = (); 1268 } 1269 } 1270 1271 # find out if the extended version of TicketGet is needed, 1272 my $Extended = $Self->_ExtendedAttributesCheck( 1273 TicketAttributes => \%TicketAttributes, 1274 ); 1275 1276 # find out if dynamic fields are required 1277 my $NeedDynamicFields = 0; 1278 DYNAMICFIELDSNEEDED: 1279 for my $ParameterName ( sort keys %TicketAttributes ) { 1280 if ( $ParameterName =~ m{\A DynamicField_ }xms ) { 1281 $NeedDynamicFields = 1; 1282 last DYNAMICFIELDSNEEDED; 1283 } 1284 } 1285 1286 my $StatsObject = $Kernel::OM->Get('Kernel::System::Stats'); 1287 1288 # generate the ticket list 1289 my @StatArray; 1290 for my $TicketID (@TicketIDs) { 1291 my @ResultRow; 1292 my %Ticket = $TicketObject->TicketGet( 1293 TicketID => $TicketID, 1294 UserID => 1, 1295 Extended => $Extended, 1296 DynamicFields => $NeedDynamicFields, 1297 ); 1298 1299 # Format Ticket 'Age' param into human readable format. 1300 $Ticket{Age} = $StatsObject->_HumanReadableAgeGet( 1301 Age => $Ticket{Age}, 1302 ); 1303 1304 # add the accounted time if needed 1305 if ( $TicketAttributes{AccountedTime} ) { 1306 $Ticket{AccountedTime} = $TicketObject->TicketAccountedTimeGet( TicketID => $TicketID ); 1307 } 1308 1309 # add the number of articles if needed 1310 if ( $TicketAttributes{NumberOfArticles} ) { 1311 $Ticket{NumberOfArticles} = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList( 1312 TicketID => $TicketID, 1313 UserID => 1 1314 ); 1315 } 1316 1317 $Ticket{Closed} ||= ''; 1318 $Ticket{SolutionTime} ||= ''; 1319 $Ticket{SolutionDiffInMin} ||= 0; 1320 $Ticket{SolutionInMin} ||= 0; 1321 $Ticket{SolutionTimeEscalation} ||= 0; 1322 $Ticket{FirstResponse} ||= ''; 1323 $Ticket{FirstResponseDiffInMin} ||= 0; 1324 $Ticket{FirstResponseInMin} ||= 0; 1325 $Ticket{FirstResponseTimeEscalation} ||= 0; 1326 $Ticket{FirstLock} ||= ''; 1327 $Ticket{UpdateTimeDestinationDate} ||= ''; 1328 $Ticket{UpdateTimeDestinationTime} ||= 0; 1329 $Ticket{UpdateTimeWorkingTime} ||= 0; 1330 $Ticket{UpdateTimeEscalation} ||= 0; 1331 $Ticket{SolutionTimeDestinationDate} ||= ''; 1332 $Ticket{EscalationDestinationIn} ||= ''; 1333 $Ticket{EscalationDestinationDate} ||= ''; 1334 $Ticket{EscalationTimeWorkingTime} ||= 0; 1335 $Ticket{NumberOfArticles} ||= 0; 1336 1337 for my $ParameterName ( sort keys %Ticket ) { 1338 if ( $ParameterName =~ m{\A DynamicField_ ( [a-zA-Z\d]+ ) \z}xms ) { 1339 1340 # loop over the dynamic fields configured 1341 DYNAMICFIELD: 1342 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 1343 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1344 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1345 1346 # skip all fields that does not match with current field name ($1) 1347 # without the 'DynamicField_' prefix 1348 next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $1; 1349 1350 # prevent unitilization errors 1351 if ( !defined $Ticket{$ParameterName} ) { 1352 $Ticket{$ParameterName} = ''; 1353 next DYNAMICFIELD; 1354 } 1355 1356 # convert from stored keys to values for certain Dynamic Fields like 1357 # Dropdown, Checkbox and Multiselect 1358 my $ValueLookup = $DynamicFieldBackendObject->ValueLookup( 1359 DynamicFieldConfig => $DynamicFieldConfig, 1360 Key => $Ticket{$ParameterName}, 1361 ); 1362 1363 # get field value in plain text 1364 my $ValueStrg = $DynamicFieldBackendObject->ReadableValueRender( 1365 DynamicFieldConfig => $DynamicFieldConfig, 1366 Value => $ValueLookup, 1367 ); 1368 1369 if ( $ValueStrg->{Value} ) { 1370 1371 # change raw value from ticket to a plain text value 1372 $Ticket{$ParameterName} = $ValueStrg->{Value}; 1373 1374 ## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject) 1375 if ( $DynamicFieldConfig->{Name} =~ /ProcessManagementProcessID|ProcessManagementActivityID/ ) { 1376 my $DisplayValue = $DynamicFieldBackendObject->DisplayValueRender( 1377 DynamicFieldConfig => $DynamicFieldConfig, 1378 Value => $ValueStrg->{Value}, 1379 ValueMaxChars => 20, 1380 LayoutObject => $Kernel::OM->Get('Kernel::Output::HTML::Layout'), 1381 HTMLOutput => 0, 1382 ); 1383 $Ticket{$ParameterName} = $DisplayValue->{Value}; 1384 } 1385 1386 } 1387 } 1388 } 1389 } 1390 1391 ATTRIBUTE: 1392 for my $Attribute ( @{$SortedAttributesRef} ) { 1393 next ATTRIBUTE if !$TicketAttributes{$Attribute}; 1394 1395 # convert from OTRS time zone to given time zone 1396 if ( 1397 $Param{TimeZone} 1398 && $Param{TimeZone} ne Kernel::System::DateTime->OTRSTimeZoneGet() 1399 && $Ticket{$Attribute} 1400 && $Ticket{$Attribute} =~ /\A(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\z/ 1401 ) 1402 { 1403 1404 $Ticket{$Attribute} = $StatsObject->_FromOTRSTimeZone( 1405 String => $Ticket{$Attribute}, 1406 TimeZone => $Param{TimeZone}, 1407 ); 1408 $Ticket{$Attribute} .= " ($Param{TimeZone})"; 1409 } 1410 1411 if ( $Attribute eq 'Owner' || $Attribute eq 'Responsible' ) { 1412 $Ticket{$Attribute} = $Kernel::OM->Get('Kernel::System::User')->UserName( 1413 User => $Ticket{$Attribute}, 1414 NoOutOfOffice => 1, 1415 ); 1416 } 1417 1418 push @ResultRow, $Ticket{$Attribute}; 1419 } 1420 push @StatArray, \@ResultRow; 1421 } 1422 1423 # use a individual sort if the sort mechanism of the TicketSearch is not usable 1424 if ( !$OrderByIsValueOfTicketSearchSort ) { 1425 @StatArray = $Self->_IndividualResultOrder( 1426 StatArray => \@StatArray, 1427 OrderBy => $OrderBy, 1428 Sort => $Sort, 1429 SelectedAttributes => \%TicketAttributes, 1430 Limit => $Limit, 1431 ); 1432 } 1433 1434 # add a enumeration in front of each row 1435 if ($AddEnumeration) { 1436 my $Counter = 0; 1437 for my $Row (@StatArray) { 1438 unshift @{$Row}, ++$Counter; 1439 } 1440 } 1441 1442 return @StatArray; 1443} 1444 1445sub GetHeaderLine { 1446 my ( $Self, %Param ) = @_; 1447 my %SelectedAttributes = map { $_ => 1 } @{ $Param{XValue}{SelectedValues} }; 1448 1449 my $TicketAttributes = $Self->_TicketAttributes(); 1450 my $SortedAttributesRef = $Self->_SortedAttributes(); 1451 my @HeaderLine; 1452 1453 # get language object 1454 my $LanguageObject = $Kernel::OM->Get('Kernel::Language'); 1455 1456 ATTRIBUTE: 1457 for my $Attribute ( @{$SortedAttributesRef} ) { 1458 next ATTRIBUTE if !$SelectedAttributes{$Attribute}; 1459 push @HeaderLine, $LanguageObject->Translate( $TicketAttributes->{$Attribute} ); 1460 } 1461 return \@HeaderLine; 1462} 1463 1464sub ExportWrapper { 1465 my ( $Self, %Param ) = @_; 1466 1467 # get needed objects 1468 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 1469 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1470 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 1471 my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); 1472 1473 # wrap ids to used spelling 1474 for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) { 1475 ELEMENT: 1476 for my $Element ( @{ $Param{$Use} } ) { 1477 next ELEMENT if !$Element || !$Element->{SelectedValues}; 1478 my $ElementName = $Element->{Element}; 1479 my $Values = $Element->{SelectedValues}; 1480 1481 if ( $ElementName eq 'QueueIDs' || $ElementName eq 'CreatedQueueIDs' ) { 1482 ID: 1483 for my $ID ( @{$Values} ) { 1484 next ID if !$ID; 1485 $ID->{Content} = $QueueObject->QueueLookup( QueueID => $ID->{Content} ); 1486 } 1487 } 1488 elsif ( $ElementName eq 'StateIDs' || $ElementName eq 'CreatedStateIDs' ) { 1489 my %StateList = $StateObject->StateList( UserID => 1 ); 1490 ID: 1491 for my $ID ( @{$Values} ) { 1492 next ID if !$ID; 1493 $ID->{Content} = $StateList{ $ID->{Content} }; 1494 } 1495 } 1496 elsif ( $ElementName eq 'PriorityIDs' || $ElementName eq 'CreatedPriorityIDs' ) { 1497 my %PriorityList = $PriorityObject->PriorityList( UserID => 1 ); 1498 ID: 1499 for my $ID ( @{$Values} ) { 1500 next ID if !$ID; 1501 $ID->{Content} = $PriorityList{ $ID->{Content} }; 1502 } 1503 } 1504 elsif ( 1505 $ElementName eq 'OwnerIDs' 1506 || $ElementName eq 'CreatedUserIDs' 1507 || $ElementName eq 'ResponsibleIDs' 1508 ) 1509 { 1510 ID: 1511 for my $ID ( @{$Values} ) { 1512 next ID if !$ID; 1513 $ID->{Content} = $UserObject->UserLookup( UserID => $ID->{Content} ); 1514 } 1515 } 1516 1517 # Locks and statustype don't have to wrap because they are never different 1518 } 1519 } 1520 return \%Param; 1521} 1522 1523sub ImportWrapper { 1524 my ( $Self, %Param ) = @_; 1525 1526 # get needed objects 1527 my $UserObject = $Kernel::OM->Get('Kernel::System::User'); 1528 my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue'); 1529 my $StateObject = $Kernel::OM->Get('Kernel::System::State'); 1530 my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority'); 1531 1532 # wrap used spelling to ids 1533 for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) { 1534 ELEMENT: 1535 for my $Element ( @{ $Param{$Use} } ) { 1536 next ELEMENT if !$Element || !$Element->{SelectedValues}; 1537 my $ElementName = $Element->{Element}; 1538 my $Values = $Element->{SelectedValues}; 1539 1540 if ( $ElementName eq 'QueueIDs' || $ElementName eq 'CreatedQueueIDs' ) { 1541 ID: 1542 for my $ID ( @{$Values} ) { 1543 next ID if !$ID; 1544 if ( $QueueObject->QueueLookup( Queue => $ID->{Content} ) ) { 1545 $ID->{Content} = $QueueObject->QueueLookup( Queue => $ID->{Content} ); 1546 } 1547 else { 1548 $Kernel::OM->Get('Kernel::System::Log')->Log( 1549 Priority => 'error', 1550 Message => "Import: Can' find the queue $ID->{Content}!" 1551 ); 1552 $ID = undef; 1553 } 1554 } 1555 } 1556 elsif ( $ElementName eq 'StateIDs' || $ElementName eq 'CreatedStateIDs' ) { 1557 ID: 1558 for my $ID ( @{$Values} ) { 1559 next ID if !$ID; 1560 1561 my %State = $StateObject->StateGet( 1562 Name => $ID->{Content}, 1563 Cache => 1, 1564 ); 1565 if ( $State{ID} ) { 1566 $ID->{Content} = $State{ID}; 1567 } 1568 else { 1569 $Kernel::OM->Get('Kernel::System::Log')->Log( 1570 Priority => 'error', 1571 Message => "Import: Can' find state $ID->{Content}!" 1572 ); 1573 $ID = undef; 1574 } 1575 } 1576 } 1577 elsif ( $ElementName eq 'PriorityIDs' || $ElementName eq 'CreatedPriorityIDs' ) { 1578 my %PriorityList = $PriorityObject->PriorityList( UserID => 1 ); 1579 my %PriorityIDs; 1580 for my $Key ( sort keys %PriorityList ) { 1581 $PriorityIDs{ $PriorityList{$Key} } = $Key; 1582 } 1583 ID: 1584 for my $ID ( @{$Values} ) { 1585 next ID if !$ID; 1586 1587 if ( $PriorityIDs{ $ID->{Content} } ) { 1588 $ID->{Content} = $PriorityIDs{ $ID->{Content} }; 1589 } 1590 else { 1591 $Kernel::OM->Get('Kernel::System::Log')->Log( 1592 Priority => 'error', 1593 Message => "Import: Can' find priority $ID->{Content}!" 1594 ); 1595 $ID = undef; 1596 } 1597 } 1598 } 1599 elsif ( 1600 $ElementName eq 'OwnerIDs' 1601 || $ElementName eq 'CreatedUserIDs' 1602 || $ElementName eq 'ResponsibleIDs' 1603 ) 1604 { 1605 ID: 1606 for my $ID ( @{$Values} ) { 1607 next ID if !$ID; 1608 1609 if ( $UserObject->UserLookup( UserLogin => $ID->{Content} ) ) { 1610 $ID->{Content} = $UserObject->UserLookup( 1611 UserLogin => $ID->{Content} 1612 ); 1613 } 1614 else { 1615 $Kernel::OM->Get('Kernel::System::Log')->Log( 1616 Priority => 'error', 1617 Message => "Import: Can' find user $ID->{Content}!" 1618 ); 1619 $ID = undef; 1620 } 1621 } 1622 } 1623 1624 # Locks and statustype don't have to wrap because they are never different 1625 } 1626 } 1627 return \%Param; 1628} 1629 1630sub _TicketAttributes { 1631 my $Self = shift; 1632 1633 # get config object 1634 my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); 1635 1636 my %TicketAttributes = ( 1637 Number => Translatable('Number'), # only a counter for a better readability 1638 TicketNumber => $ConfigObject->Get('Ticket::Hook'), 1639 1640 #TicketID => 'TicketID', 1641 Age => 'Age', 1642 Title => 'Title', 1643 Queue => 'Queue', 1644 1645 #QueueID => 'QueueID', 1646 State => 'State', 1647 1648 #StateID => 'StateID', 1649 Priority => 'Priority', 1650 1651 #PriorityID => 'PriorityID', 1652 CustomerID => 'Customer ID', 1653 Changed => Translatable('Last Changed'), 1654 Created => 'Created', 1655 1656 CustomerUserID => 'Customer User', 1657 Lock => 'Lock', 1658 1659 #LockID => 'LockID', 1660 UnlockTimeout => 'UnlockTimeout', 1661 AccountedTime => 'Accounted time', # the same wording is in AgentTicketPrint.tt 1662 RealTillTimeNotUsed => 'RealTillTimeNotUsed', 1663 NumberOfArticles => 'Number of Articles', 1664 1665 #GroupID => 'GroupID', 1666 StateType => 'StateType', 1667 UntilTime => 'UntilTime', 1668 Closed => 'Close Time', 1669 FirstLock => 'First Lock', 1670 1671 EscalationResponseTime => 'EscalationResponseTime', 1672 EscalationUpdateTime => 'EscalationUpdateTime', 1673 EscalationSolutionTime => 'EscalationSolutionTime', 1674 1675 EscalationDestinationIn => 'EscalationDestinationIn', 1676 1677 # EscalationDestinationTime => 'EscalationDestinationTime', 1678 EscalationDestinationDate => 'EscalationDestinationDate', 1679 EscalationTimeWorkingTime => 'EscalationTimeWorkingTime', 1680 EscalationTime => 'EscalationTime', 1681 1682 FirstResponse => 'FirstResponse', 1683 FirstResponseInMin => 'FirstResponseInMin', 1684 FirstResponseDiffInMin => 'FirstResponseDiffInMin', 1685 FirstResponseTimeWorkingTime => 'FirstResponseTimeWorkingTime', 1686 FirstResponseTimeEscalation => 'FirstResponseTimeEscalation', 1687 FirstResponseTimeNotification => 'FirstResponseTimeNotification', 1688 FirstResponseTimeDestinationTime => 'FirstResponseTimeDestinationTime', 1689 FirstResponseTimeDestinationDate => 'FirstResponseTimeDestinationDate', 1690 FirstResponseTime => 'FirstResponseTime', 1691 1692 UpdateTimeEscalation => 'UpdateTimeEscalation', 1693 UpdateTimeNotification => 'UpdateTimeNotification', 1694 UpdateTimeDestinationTime => 'UpdateTimeDestinationTime', 1695 UpdateTimeDestinationDate => 'UpdateTimeDestinationDate', 1696 UpdateTimeWorkingTime => 'UpdateTimeWorkingTime', 1697 UpdateTime => 'UpdateTime', 1698 1699 SolutionTime => 'SolutionTime', 1700 SolutionInMin => 'SolutionInMin', 1701 SolutionDiffInMin => 'SolutionDiffInMin', 1702 SolutionTimeWorkingTime => 'SolutionTimeWorkingTime', 1703 SolutionTimeEscalation => 'SolutionTimeEscalation', 1704 SolutionTimeNotification => 'SolutionTimeNotification', 1705 SolutionTimeDestinationTime => 'SolutionTimeDestinationTime', 1706 SolutionTimeDestinationDate => 'SolutionTimeDestinationDate', 1707 SolutionTimeWorkingTime => 'SolutionTimeWorkingTime', 1708 ); 1709 1710 if ( $ConfigObject->Get('Ticket::Service') ) { 1711 $TicketAttributes{Service} = 'Service'; 1712 1713 #$TicketAttributes{ServiceID} = 'ServiceID', 1714 $TicketAttributes{SLA} = 'SLA'; 1715 $TicketAttributes{SLAID} = 'SLAID'; 1716 } 1717 1718 if ( $ConfigObject->Get('Ticket::Type') ) { 1719 $TicketAttributes{Type} = 'Type'; 1720 1721 #$TicketAttributes{TypeID} = 'TypeID'; 1722 } 1723 1724 if ( $ConfigObject->Get('Stats::UseAgentElementInStats') ) { 1725 $TicketAttributes{Owner} = 'Agent/Owner'; 1726 1727 #$TicketAttributes{OwnerID} = 'OwnerID'; 1728 # ? $TicketAttributes{CreatedUserIDs} = 'Created by Agent/Owner'; 1729 $TicketAttributes{Responsible} = 'Responsible'; 1730 1731 #$TicketAttributes{ResponsibleID} = 'ResponsibleID'; 1732 } 1733 1734 DYNAMICFIELD: 1735 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 1736 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1737 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1738 1739 $TicketAttributes{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $DynamicFieldConfig->{Label}; 1740 } 1741 1742 return \%TicketAttributes; 1743} 1744 1745sub _SortedAttributes { 1746 my $Self = shift; 1747 1748 my @SortedAttributes = qw( 1749 Number 1750 TicketNumber 1751 Age 1752 Title 1753 Created 1754 Changed 1755 Closed 1756 Queue 1757 State 1758 Priority 1759 CustomerUserID 1760 CustomerID 1761 Service 1762 SLA 1763 Type 1764 Owner 1765 Responsible 1766 AccountedTime 1767 EscalationDestinationIn 1768 EscalationDestinationTime 1769 EscalationDestinationDate 1770 EscalationTimeWorkingTime 1771 EscalationTime 1772 1773 FirstResponse 1774 FirstResponseInMin 1775 FirstResponseDiffInMin 1776 FirstResponseTimeWorkingTime 1777 FirstResponseTimeEscalation 1778 FirstResponseTimeNotification 1779 FirstResponseTimeDestinationTime 1780 FirstResponseTimeDestinationDate 1781 FirstResponseTime 1782 1783 UpdateTimeEscalation 1784 UpdateTimeNotification 1785 UpdateTimeDestinationTime 1786 UpdateTimeDestinationDate 1787 UpdateTimeWorkingTime 1788 UpdateTime 1789 1790 SolutionTime 1791 SolutionInMin 1792 SolutionDiffInMin 1793 SolutionTimeWorkingTime 1794 SolutionTimeEscalation 1795 SolutionTimeNotification 1796 SolutionTimeDestinationTime 1797 SolutionTimeDestinationDate 1798 SolutionTimeWorkingTime 1799 1800 FirstLock 1801 Lock 1802 StateType 1803 UntilTime 1804 UnlockTimeout 1805 EscalationResponseTime 1806 EscalationSolutionTime 1807 EscalationUpdateTime 1808 RealTillTimeNotUsed 1809 NumberOfArticles 1810 ); 1811 1812 # cycle trought the Dynamic Fields 1813 DYNAMICFIELD: 1814 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 1815 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1816 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1817 1818 # add dynamic field attribute 1819 push @SortedAttributes, 'DynamicField_' . $DynamicFieldConfig->{Name}; 1820 } 1821 1822 return \@SortedAttributes; 1823} 1824 1825sub _ExtendedAttributesCheck { 1826 my ( $Self, %Param ) = @_; 1827 1828 my @ExtendedAttributes = qw( 1829 FirstResponse 1830 FirstResponseInMin 1831 FirstResponseDiffInMin 1832 FirstResponseTimeWorkingTime 1833 Closed 1834 SolutionTime 1835 SolutionInMin 1836 SolutionDiffInMin 1837 SolutionTimeWorkingTime 1838 FirstLock 1839 ); 1840 1841 ATTRIBUTE: 1842 for my $Attribute (@ExtendedAttributes) { 1843 return 1 if $Param{TicketAttributes}{$Attribute}; 1844 } 1845 1846 return; 1847} 1848 1849sub _OrderByIsValueOfTicketSearchSort { 1850 my ( $Self, %Param ) = @_; 1851 1852 my %SortOptions = ( 1853 Age => 'Age', 1854 Created => 'Age', 1855 CustomerID => 'CustomerID', 1856 EscalationResponseTime => 'EscalationResponseTime', 1857 EscalationSolutionTime => 'EscalationSolutionTime', 1858 EscalationTime => 'EscalationTime', 1859 EscalationUpdateTime => 'EscalationUpdateTime', 1860 Lock => 'Lock', 1861 Owner => 'Owner', 1862 Priority => 'Priority', 1863 Queue => 'Queue', 1864 Responsible => 'Responsible', 1865 SLA => 'SLA', 1866 Service => 'Service', 1867 State => 'State', 1868 TicketNumber => 'Ticket', 1869 TicketEscalation => 'TicketEscalation', 1870 Title => 'Title', 1871 Type => 'Type', 1872 ); 1873 1874 # get dynamic field backend object 1875 my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); 1876 1877 # cycle trought the Dynamic Fields 1878 DYNAMICFIELD: 1879 for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) { 1880 next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); 1881 next DYNAMICFIELD if !$DynamicFieldConfig->{Name}; 1882 1883 # get dynamic field sortable condition 1884 my $IsSortable = $DynamicFieldBackendObject->HasBehavior( 1885 DynamicFieldConfig => $DynamicFieldConfig, 1886 Behavior => 'IsSortable', 1887 ); 1888 1889 # add dynamic field if is sortable 1890 if ($IsSortable) { 1891 $SortOptions{ 'DynamicField_' . $DynamicFieldConfig->{Name} } 1892 = 'DynamicField_' . $DynamicFieldConfig->{Name}; 1893 } 1894 } 1895 1896 return $SortOptions{ $Param{OrderBy} } if $SortOptions{ $Param{OrderBy} }; 1897 return; 1898} 1899 1900sub _IndividualResultOrder { 1901 my ( $Self, %Param ) = @_; 1902 my @Unsorted = @{ $Param{StatArray} }; 1903 my @Sorted; 1904 1905 # find out the positon of the values which should be 1906 # used for the order 1907 my $Counter = 0; 1908 my $SortedAttributes = $Self->_SortedAttributes(); 1909 1910 ATTRIBUTE: 1911 for my $Attribute ( @{$SortedAttributes} ) { 1912 next ATTRIBUTE if !$Param{SelectedAttributes}{$Attribute}; 1913 last ATTRIBUTE if $Attribute eq $Param{OrderBy}; 1914 $Counter++; 1915 } 1916 1917 # order after a individual attribute 1918 if ( $Param{OrderBy} eq 'AccountedTime' ) { 1919 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1920 } 1921 elsif ( $Param{OrderBy} eq 'SolutionTime' ) { 1922 @Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted; 1923 } 1924 elsif ( $Param{OrderBy} eq 'SolutionDiffInMin' ) { 1925 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1926 } 1927 elsif ( $Param{OrderBy} eq 'SolutionInMin' ) { 1928 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1929 } 1930 elsif ( $Param{OrderBy} eq 'SolutionTimeWorkingTime' ) { 1931 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1932 } 1933 elsif ( $Param{OrderBy} eq 'FirstResponse' ) { 1934 @Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted; 1935 } 1936 elsif ( $Param{OrderBy} eq 'FirstResponseDiffInMin' ) { 1937 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1938 } 1939 elsif ( $Param{OrderBy} eq 'FirstResponseInMin' ) { 1940 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1941 } 1942 elsif ( $Param{OrderBy} eq 'FirstResponseTimeWorkingTime' ) { 1943 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1944 } 1945 elsif ( $Param{OrderBy} eq 'FirstLock' ) { 1946 @Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted; 1947 } 1948 elsif ( $Param{OrderBy} eq 'StateType' ) { 1949 @Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted; 1950 } 1951 elsif ( $Param{OrderBy} eq 'UntilTime' ) { 1952 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1953 } 1954 elsif ( $Param{OrderBy} eq 'UnlockTimeout' ) { 1955 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1956 } 1957 elsif ( $Param{OrderBy} eq 'EscalationResponseTime' ) { 1958 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1959 } 1960 elsif ( $Param{OrderBy} eq 'EscalationUpdateTime' ) { 1961 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1962 } 1963 elsif ( $Param{OrderBy} eq 'EscalationSolutionTime' ) { 1964 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1965 } 1966 elsif ( $Param{OrderBy} eq 'RealTillTimeNotUsed' ) { 1967 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1968 } 1969 elsif ( $Param{OrderBy} eq 'EscalationTimeWorkingTime' ) { 1970 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1971 } 1972 elsif ( $Param{OrderBy} eq 'NumberOfArticles' ) { 1973 @Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted; 1974 } 1975 else { 1976 @Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted; 1977 } 1978 1979 # make a reverse sort if needed 1980 if ( $Param{Sort} eq 'Down' ) { 1981 @Sorted = reverse @Sorted; 1982 } 1983 1984 # take care about the limit 1985 if ( $Param{Limit} && $Param{Limit} ne 'unlimited' ) { 1986 my $Count = 0; 1987 @Sorted = grep { ++$Count <= $Param{Limit} } @Sorted; 1988 } 1989 1990 return @Sorted; 1991} 1992 19931; 1994