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