Files
2024-10-14 00:08:40 +02:00

1994 lines
71 KiB
Perl

# --
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (GPL). If you
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
# --
package Kernel::System::Stats::Dynamic::TicketList;
use strict;
use warnings;
use List::Util qw( first );
use Kernel::System::VariableCheck qw(:all);
use Kernel::Language qw(Translatable);
our @ObjectDependencies = (
'Kernel::Config',
'Kernel::Language',
'Kernel::System::DB',
'Kernel::System::DynamicField',
'Kernel::System::DynamicField::Backend',
'Kernel::System::Lock',
'Kernel::System::Log',
'Kernel::System::Priority',
'Kernel::System::Queue',
'Kernel::System::Service',
'Kernel::System::SLA',
'Kernel::System::State',
'Kernel::System::Stats',
'Kernel::System::Ticket',
'Kernel::System::Ticket::Article',
'Kernel::System::DateTime',
'Kernel::System::Type',
'Kernel::System::User',
'Kernel::Output::HTML::Layout',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# get the dynamic fields for ticket object
$Self->{DynamicField} = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet(
Valid => 1,
ObjectType => ['Ticket'],
);
return $Self;
}
sub GetObjectName {
my ( $Self, %Param ) = @_;
return 'Ticketlist';
}
sub GetObjectBehaviours {
my ( $Self, %Param ) = @_;
my %Behaviours = (
ProvidesDashboardWidget => 0,
);
return %Behaviours;
}
sub GetObjectAttributes {
my ( $Self, %Param ) = @_;
# get needed objects
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
my $LockObject = $Kernel::OM->Get('Kernel::System::Lock');
my $ValidAgent = 0;
if (
defined $ConfigObject->Get('Stats::UseInvalidAgentInStats')
&& ( $ConfigObject->Get('Stats::UseInvalidAgentInStats') == 0 )
)
{
$ValidAgent = 1;
}
# Get user list without the out of office message, because of the caching in the statistics
# and not meaningful with a date selection.
my %UserList = $UserObject->UserList(
Type => 'Long',
Valid => $ValidAgent,
NoOutOfOffice => 1,
);
# get state list
my %StateList = $StateObject->StateList(
UserID => 1,
);
# get state type list
my %StateTypeList = $StateObject->StateTypeList(
UserID => 1,
);
# get queue list
my %QueueList = $QueueObject->GetAllQueues();
# get priority list
my %PriorityList = $PriorityObject->PriorityList(
UserID => 1,
);
# get lock list
my %LockList = $LockObject->LockList(
UserID => 1,
);
my %Limit = (
5 => 5,
10 => 10,
20 => 20,
50 => 50,
100 => 100,
unlimited => Translatable('unlimited'),
);
my %TicketAttributes = %{ $Self->_TicketAttributes() };
my %OrderBy = map { $_ => $TicketAttributes{$_} } grep { $_ ne 'Number' } keys %TicketAttributes;
# get dynamic field backend object
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
# remove non sortable (and orderable) Dynamic Fields
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# check if dynamic field is sortable
my $IsSortable = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsSortable',
);
# remove dynamic fields from the list if is not sortable
if ( !$IsSortable ) {
delete $OrderBy{ 'DynamicField_' . $DynamicFieldConfig->{Name} };
}
}
my %SortSequence = (
Up => Translatable('ascending'),
Down => Translatable('descending'),
);
my @ObjectAttributes = (
{
Name => Translatable('Attributes to be printed'),
UseAsXvalue => 1,
UseAsValueSeries => 0,
UseAsRestriction => 0,
Element => 'TicketAttributes',
Block => 'MultiSelectField',
Translation => 1,
Values => \%TicketAttributes,
Sort => 'IndividualKey',
SortIndividual => $Self->_SortedAttributes(),
},
{
Name => Translatable('Order by'),
UseAsXvalue => 0,
UseAsValueSeries => 1,
UseAsRestriction => 0,
Element => 'OrderBy',
Block => 'SelectField',
Translation => 1,
Values => \%OrderBy,
Sort => 'IndividualKey',
SortIndividual => $Self->_SortedAttributes(),
},
{
Name => Translatable('Sort sequence'),
UseAsXvalue => 0,
UseAsValueSeries => 1,
UseAsRestriction => 0,
Element => 'SortSequence',
Block => 'SelectField',
Translation => 1,
Values => \%SortSequence,
},
{
Name => Translatable('Limit'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'Limit',
Block => 'SelectField',
Translation => 1,
Values => \%Limit,
Sort => 'IndividualKey',
SortIndividual => [ '5', '10', '20', '50', '100', 'unlimited', ],
},
{
Name => Translatable('Queue'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'QueueIDs',
Block => 'MultiSelectField',
Translation => 0,
TreeView => 1,
Values => \%QueueList,
},
{
Name => Translatable('State'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'StateIDs',
Block => 'MultiSelectField',
Values => \%StateList,
},
{
Name => Translatable('State Historic'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'StateIDsHistoric',
Block => 'MultiSelectField',
Values => \%StateList,
},
{
Name => Translatable('State Type'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'StateTypeIDs',
Block => 'MultiSelectField',
Values => \%StateTypeList,
},
{
Name => Translatable('State Type Historic'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'StateTypeIDsHistoric',
Block => 'MultiSelectField',
Values => \%StateTypeList,
},
{
Name => Translatable('Priority'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'PriorityIDs',
Block => 'MultiSelectField',
Values => \%PriorityList,
},
{
Name => Translatable('Created in Queue'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CreatedQueueIDs',
Block => 'MultiSelectField',
Translation => 0,
TreeView => 1,
Values => \%QueueList,
},
{
Name => Translatable('Created Priority'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CreatedPriorityIDs',
Block => 'MultiSelectField',
Values => \%PriorityList,
},
{
Name => Translatable('Created State'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CreatedStateIDs',
Block => 'MultiSelectField',
Values => \%StateList,
},
{
Name => Translatable('Lock'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'LockIDs',
Block => 'MultiSelectField',
Values => \%LockList,
},
{
Name => Translatable('Title'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'Title',
Block => 'InputField',
},
{
Name => Translatable('From'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'MIMEBase_From',
Block => 'InputField',
},
{
Name => Translatable('To'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'MIMEBase_To',
Block => 'InputField',
},
{
Name => Translatable('Cc'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'MIMEBase_Cc',
Block => 'InputField',
},
{
Name => Translatable('Subject'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'MIMEBase_Subject',
Block => 'InputField',
},
{
Name => Translatable('Text'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'MIMEBase_Body',
Block => 'InputField',
},
{
Name => Translatable('Create Time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CreateTime',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TicketCreateTimeNewerDate',
TimeStop => 'TicketCreateTimeOlderDate',
},
},
{
Name => Translatable('Last changed times'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'LastChangeTime',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TicketLastChangeTimeNewerDate',
TimeStop => 'TicketLastChangeTimeOlderDate',
},
},
{
Name => Translatable('Change times'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'ChangeTime',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TicketChangeTimeNewerDate',
TimeStop => 'TicketChangeTimeOlderDate',
},
},
{
Name => Translatable('Pending until time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'PendingUntilTime',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TicketPendingTimeNewerDate',
TimeStop => 'TicketPendingTimeOlderDate',
},
},
{
Name => Translatable('Close Time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CloseTime',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TicketCloseTimeNewerDate',
TimeStop => 'TicketCloseTimeOlderDate',
},
},
{
Name => Translatable('Historic Time Range'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'HistoricTimeRange',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'HistoricTimeRangeTimeNewerDate',
TimeStop => 'HistoricTimeRangeTimeOlderDate',
},
},
{
Name => Translatable('Escalation'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'EscalationTime',
TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat',
Block => 'Time',
Values => {
TimeStart => 'TicketEscalationTimeNewerDate',
TimeStop => 'TicketEscalationTimeOlderDate',
},
},
{
Name => Translatable('Escalation - First Response Time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'EscalationResponseTime',
TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat',
Block => 'Time',
Values => {
TimeStart => 'TicketEscalationResponseTimeNewerDate',
TimeStop => 'TicketEscalationResponseTimeOlderDate',
},
},
{
Name => Translatable('Escalation - Update Time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'EscalationUpdateTime',
TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat',
Block => 'Time',
Values => {
TimeStart => 'TicketEscalationUpdateTimeNewerDate',
TimeStop => 'TicketEscalationUpdateTimeOlderDate',
},
},
{
Name => Translatable('Escalation - Solution Time'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'EscalationSolutionTime',
TimePeriodFormat => 'DateInputFormatLong', # 'DateInputFormat',
Block => 'Time',
Values => {
TimeStart => 'TicketEscalationSolutionTimeNewerDate',
TimeStop => 'TicketEscalationSolutionTimeOlderDate',
},
},
);
if ( $ConfigObject->Get('Ticket::Service') ) {
# get service list
my %Service = $Kernel::OM->Get('Kernel::System::Service')->ServiceList(
KeepChildren => $ConfigObject->Get('Ticket::Service::KeepChildren'),
UserID => 1,
);
# get sla list
my %SLA = $Kernel::OM->Get('Kernel::System::SLA')->SLAList(
UserID => 1,
);
my @ObjectAttributeAdd = (
{
Name => Translatable('Service'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'ServiceIDs',
Block => 'MultiSelectField',
Translation => 0,
TreeView => 1,
Values => \%Service,
},
{
Name => Translatable('SLA'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'SLAIDs',
Block => 'MultiSelectField',
Translation => 0,
Values => \%SLA,
},
);
unshift @ObjectAttributes, @ObjectAttributeAdd;
}
if ( $ConfigObject->Get('Ticket::Type') ) {
# get ticket type list
my %Type = $Kernel::OM->Get('Kernel::System::Type')->TypeList(
UserID => 1,
);
my %ObjectAttribute1 = (
Name => Translatable('Type'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'TypeIDs',
Block => 'MultiSelectField',
Translation => 0,
Values => \%Type,
);
unshift @ObjectAttributes, \%ObjectAttribute1;
}
if ( $ConfigObject->Get('Stats::UseAgentElementInStats') ) {
my @ObjectAttributeAdd = (
{
Name => Translatable('Agent/Owner'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'OwnerIDs',
Block => 'MultiSelectField',
Translation => 0,
Values => \%UserList,
},
{
Name => Translatable('Created by Agent/Owner'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CreatedUserIDs',
Block => 'MultiSelectField',
Translation => 0,
Values => \%UserList,
},
{
Name => Translatable('Responsible'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'ResponsibleIDs',
Block => 'MultiSelectField',
Translation => 0,
Values => \%UserList,
},
);
push @ObjectAttributes, @ObjectAttributeAdd;
}
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
if ( $ConfigObject->Get('Stats::CustomerIDAsMultiSelect') ) {
# Get CustomerID
# (This way also can be the solution for the CustomerUserID)
$DBObject->Prepare(
SQL => "SELECT DISTINCT customer_id FROM ticket",
);
# fetch the result
my %CustomerID;
while ( my @Row = $DBObject->FetchrowArray() ) {
if ( $Row[0] ) {
$CustomerID{ $Row[0] } = $Row[0];
}
}
my %ObjectAttribute = (
Name => Translatable('Customer ID'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerID',
Block => 'MultiSelectField',
Values => \%CustomerID,
);
push @ObjectAttributes, \%ObjectAttribute;
}
else {
my @CustomerIDAttributes = (
{
Name => Translatable('CustomerID (complex search)'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerID',
Block => 'InputField',
},
{
Name => Translatable('CustomerID (exact match)'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerIDRaw',
Block => 'InputField',
},
);
push @ObjectAttributes, @CustomerIDAttributes;
}
if ( $ConfigObject->Get('Stats::CustomerUserLoginsAsMultiSelect') ) {
# Get all CustomerUserLogins which are related to a tiket.
$DBObject->Prepare(
SQL => "SELECT DISTINCT customer_user_id FROM ticket",
);
# fetch the result
my %CustomerUserIDs;
while ( my @Row = $DBObject->FetchrowArray() ) {
if ( $Row[0] ) {
$CustomerUserIDs{ $Row[0] } = $Row[0];
}
}
my %ObjectAttribute = (
Name => Translatable('Assigned to Customer User Login'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerUserLoginRaw',
Block => 'MultiSelectField',
Values => \%CustomerUserIDs,
);
push @ObjectAttributes, \%ObjectAttribute;
}
else {
my @CustomerUserAttributes = (
{
Name => Translatable('Assigned to Customer User Login (complex search)'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerUserLogin',
Block => 'InputField',
},
{
Name => Translatable('Assigned to Customer User Login (exact match)'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerUserLoginRaw',
Block => 'InputField',
CSSClass => 'CustomerAutoCompleteSimple',
HTMLDataAttributes => {
'customer-search-type' => 'CustomerUser',
},
},
);
push @ObjectAttributes, @CustomerUserAttributes;
}
# Add always the field for the customer user login accessible tickets as auto complete field.
my %ObjectAttribute = (
Name => Translatable('Accessible to Customer User Login (exact match)'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'CustomerUserID',
Block => 'InputField',
CSSClass => 'CustomerAutoCompleteSimple',
HTMLDataAttributes => {
'customer-search-type' => 'CustomerUser',
},
);
push @ObjectAttributes, \%ObjectAttribute;
if ( $ConfigObject->Get('Ticket::ArchiveSystem') ) {
my %ObjectAttribute = (
Name => Translatable('Archive Search'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'SearchInArchive',
Block => 'SelectField',
Translation => 1,
Values => {
ArchivedTickets => Translatable('Archived tickets'),
NotArchivedTickets => Translatable('Unarchived tickets'),
AllTickets => Translatable('All tickets'),
},
);
push @ObjectAttributes, \%ObjectAttribute;
}
# cycle trough the activated Dynamic Fields for this screen
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
# skip all fields not designed to be supported by statistics
my $IsStatsCondition = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsStatsCondition',
);
next DYNAMICFIELD if !$IsStatsCondition;
my $PossibleValuesFilter;
my $IsACLReducible = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsACLReducible',
);
if ($IsACLReducible) {
# get PossibleValues
my $PossibleValues = $DynamicFieldBackendObject->PossibleValuesGet(
DynamicFieldConfig => $DynamicFieldConfig,
);
# convert possible values key => value to key => key for ACLs using a Hash slice
my %AclData = %{ $PossibleValues || {} };
@AclData{ keys %AclData } = keys %AclData;
# set possible values filter from ACLs
my $ACL = $TicketObject->TicketAcl(
Action => 'AgentStats',
Type => 'DynamicField_' . $DynamicFieldConfig->{Name},
ReturnType => 'Ticket',
ReturnSubType => 'DynamicField_' . $DynamicFieldConfig->{Name},
Data => \%AclData || {},
UserID => 1,
);
if ($ACL) {
my %Filter = $TicketObject->TicketAclData();
# convert Filer key => key back to key => value using map
%{$PossibleValuesFilter} = map { $_ => $PossibleValues->{$_} } keys %Filter;
}
}
# get dynamic field stats parameters
my $DynamicFieldStatsParameter = $DynamicFieldBackendObject->StatsFieldParameterBuild(
DynamicFieldConfig => $DynamicFieldConfig,
PossibleValuesFilter => $PossibleValuesFilter,
);
if ( IsHashRefWithData($DynamicFieldStatsParameter) ) {
# backward compatibility
if ( !$DynamicFieldStatsParameter->{Block} ) {
$DynamicFieldStatsParameter->{Block} = 'InputField';
if ( IsHashRefWithData( $DynamicFieldStatsParameter->{Values} ) ) {
$DynamicFieldStatsParameter->{Block} = 'MultiSelectField';
}
}
if ( $DynamicFieldStatsParameter->{Block} eq 'Time' ) {
# create object attributes (date/time fields)
my $TimePeriodFormat = $DynamicFieldStatsParameter->{TimePeriodFormat} || 'DateInputFormatLong';
my %ObjectAttribute = (
Name => $DynamicFieldStatsParameter->{Name},
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => $DynamicFieldStatsParameter->{Element},
TimePeriodFormat => $TimePeriodFormat,
Block => $DynamicFieldStatsParameter->{Block},
TimePeriodFormat => $TimePeriodFormat,
Values => {
TimeStart =>
$DynamicFieldStatsParameter->{Element}
. '_GreaterThanEquals',
TimeStop =>
$DynamicFieldStatsParameter->{Element}
. '_SmallerThanEquals',
},
);
push @ObjectAttributes, \%ObjectAttribute;
}
elsif ( $DynamicFieldStatsParameter->{Block} eq 'MultiSelectField' ) {
# create object attributes (multiple values)
my %ObjectAttribute = (
Name => $DynamicFieldStatsParameter->{Name},
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => $DynamicFieldStatsParameter->{Element},
Block => $DynamicFieldStatsParameter->{Block},
Values => $DynamicFieldStatsParameter->{Values},
Translation => $DynamicFieldStatsParameter->{TranslatableValues} || 0,
IsDynamicField => 1,
ShowAsTree => $DynamicFieldConfig->{Config}->{TreeView} || 0,
);
push @ObjectAttributes, \%ObjectAttribute;
}
else {
# create object attributes (text fields)
my %ObjectAttribute = (
Name => $DynamicFieldStatsParameter->{Name},
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => $DynamicFieldStatsParameter->{Element},
Block => $DynamicFieldStatsParameter->{Block},
);
push @ObjectAttributes, \%ObjectAttribute;
}
}
}
return @ObjectAttributes;
}
sub GetStatTablePreview {
my ( $Self, %Param ) = @_;
return $Self->GetStatTable(
%Param,
Preview => 1,
);
}
sub GetStatTable {
my ( $Self, %Param ) = @_;
my %TicketAttributes = map { $_ => 1 } @{ $Param{XValue}{SelectedValues} };
my $SortedAttributesRef = $Self->_SortedAttributes();
my $Preview = $Param{Preview};
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
# check if a enumeration is requested
my $AddEnumeration = 0;
if ( $TicketAttributes{Number} ) {
$AddEnumeration = 1;
delete $TicketAttributes{Number};
}
# set default values if no sort or order attribute is given
my $OrderRef = first { $_->{Element} eq 'OrderBy' } @{ $Param{ValueSeries} };
my $OrderBy = $OrderRef ? $OrderRef->{SelectedValues}[0] : 'Age';
my $SortRef = first { $_->{Element} eq 'SortSequence' } @{ $Param{ValueSeries} };
my $Sort = $SortRef ? $SortRef->{SelectedValues}[0] : 'Down';
my $Limit = $Param{Restrictions}{Limit};
# check if we can use the sort and order function of TicketSearch
my $OrderByIsValueOfTicketSearchSort = $Self->_OrderByIsValueOfTicketSearchSort(
OrderBy => $OrderBy,
);
# escape search attributes for ticket search
my %AttributesToEscape = (
'CustomerID' => 1,
'Title' => 1,
);
# Map the CustomerID search parameter to CustomerIDRaw search parameter for the
# exact search match, if the 'Stats::CustomerIDAsMultiSelect' is active.
if ( $Kernel::OM->Get('Kernel::Config')->Get('Stats::CustomerIDAsMultiSelect') ) {
$Param{Restrictions}->{CustomerIDRaw} = $Param{Restrictions}->{CustomerID};
}
ATTRIBUTE:
for my $Key ( sort keys %{ $Param{Restrictions} } ) {
next ATTRIBUTE if !$AttributesToEscape{$Key};
if ( ref $Param{Restrictions}->{$Key} ) {
if ( ref $Param{Restrictions}->{$Key} eq 'ARRAY' ) {
$Param{Restrictions}->{$Key} = [
map { $DBObject->QueryStringEscape( QueryString => $_ ) }
@{ $Param{Restrictions}->{$Key} }
];
}
}
else {
$Param{Restrictions}->{$Key} = $DBObject->QueryStringEscape(
QueryString => $Param{Restrictions}->{$Key}
);
}
}
# get dynamic field backend object
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
my %DynamicFieldRestrictions;
for my $ParameterName ( sort keys %{ $Param{Restrictions} } ) {
if (
$ParameterName =~ m{ \A DynamicField_ ( [a-zA-Z\d]+ ) (?: _ ( [a-zA-Z\d]+ ) )? \z }xms
)
{
my $FieldName = $1;
my $Operator = $2;
# loop over the dynamic fields configured
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# skip all fields that do not match with current field name
# without the 'DynamicField_' prefix
next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $FieldName;
# skip all fields not designed to be supported by statistics
my $IsStatsCondition = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsStatsCondition',
);
next DYNAMICFIELD if !$IsStatsCondition;
# get new search parameter
my $DynamicFieldStatsSearchParameter = $DynamicFieldBackendObject->StatsSearchFieldParameterBuild(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $Param{Restrictions}->{$ParameterName},
Operator => $Operator,
);
# add new search parameter
if ( !IsHashRefWithData( $DynamicFieldRestrictions{"DynamicField_$FieldName"} ) ) {
$DynamicFieldRestrictions{"DynamicField_$FieldName"} =
$DynamicFieldStatsSearchParameter;
}
# extend search parameter
elsif ( IsHashRefWithData($DynamicFieldStatsSearchParameter) ) {
$DynamicFieldRestrictions{"DynamicField_$FieldName"} = {
%{ $DynamicFieldRestrictions{"DynamicField_$FieldName"} },
%{$DynamicFieldStatsSearchParameter},
};
}
}
}
}
if ($OrderByIsValueOfTicketSearchSort) {
# don't be irritated of the mixture OrderBy <> Sort and SortBy <> OrderBy
# the meaning is in TicketSearch is different as in common handling
$Param{Restrictions}{OrderBy} = $Sort;
$Param{Restrictions}{SortBy} = $OrderByIsValueOfTicketSearchSort;
$Param{Restrictions}{Limit} = !$Limit || $Limit eq 'unlimited' ? 100_000_000 : $Limit;
}
else {
$Param{Restrictions}{Limit} = 100_000_000;
}
# get state object
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
# OlderTicketsExclude for historic searches
# takes tickets that were closed before the
# start of the searched time periode
my %OlderTicketsExclude;
# NewerTicketExclude for historic searches
# takes tickets that were created after the
# searched time periode
my %NewerTicketsExclude;
my %StateList = $StateObject->StateList( UserID => 1 );
# get time object
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
# UnixTimeStart & End:
# The Time periode the historic search is executed
# if no time periode has been selected we take
# Unixtime 0 as StartTime and SystemTime as EndTime
my $UnixTimeStart = 0;
my $UnixTimeEnd = $DateTimeObject->ToEpoch();
if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) {
$Param{Restrictions}->{SearchInArchive} ||= '';
if ( $Param{Restrictions}->{SearchInArchive} eq 'AllTickets' ) {
$Param{Restrictions}->{ArchiveFlags} = [ 'y', 'n' ];
}
elsif ( $Param{Restrictions}->{SearchInArchive} eq 'ArchivedTickets' ) {
$Param{Restrictions}->{ArchiveFlags} = ['y'];
}
else {
$Param{Restrictions}->{ArchiveFlags} = ['n'];
}
}
# get ticket object
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
if ( !$Preview && $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate} ) {
# Find tickets that were closed before the start of our
# HistoricTimeRangeTimeNewerDate, these have to be excluded.
# In order to reduce it quickly we reformat the result array
# to a hash.
my @OldToExclude = $TicketObject->TicketSearch(
UserID => 1,
Result => 'ARRAY',
Permission => 'ro',
TicketCloseTimeOlderDate => $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate},
ArchiveFlags => $Param{Restrictions}->{ArchiveFlags},
Limit => 100_000_000,
);
%OlderTicketsExclude = map { $_ => 1 } @OldToExclude;
$DateTimeObject->Set( String => $Param{Restrictions}->{HistoricTimeRangeTimeNewerDate} );
$UnixTimeStart = $DateTimeObject->ToEpoch();
}
if ( !$Preview && $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate} ) {
# Find tickets that were closed after the end of our
# HistoricTimeRangeTimeOlderDate, these have to be excluded
# in order to reduce it quickly we reformat the result array
# to a hash.
my @NewToExclude = $TicketObject->TicketSearch(
UserID => 1,
Result => 'ARRAY',
Permission => 'ro',
TicketCreateTimeNewerDate => $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate},
ArchiveFlags => $Param{Restrictions}->{ArchiveFlags},
Limit => 100_000_000,
);
%NewerTicketsExclude = map { $_ => 1 } @NewToExclude;
$DateTimeObject->Set( String => $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate} );
$UnixTimeEnd = $DateTimeObject->ToEpoch();
}
# get the involved tickets
my @TicketIDs;
if ($Preview) {
@TicketIDs = $TicketObject->TicketSearch(
UserID => 1,
Result => 'ARRAY',
Permission => 'ro',
Limit => 10,
);
}
else {
@TicketIDs = $TicketObject->TicketSearch(
UserID => 1,
Result => 'ARRAY',
Permission => 'ro',
%{ $Param{Restrictions} },
%DynamicFieldRestrictions,
);
}
# if we had Tickets we need to reduce the found tickets
# to those not beeing in %OlderTicketsExclude
# as well as not in %NewerTicketsExclude
if ( %OlderTicketsExclude || %NewerTicketsExclude ) {
@TicketIDs = grep {
!defined $OlderTicketsExclude{$_}
&& !defined $NewerTicketsExclude{$_}
} @TicketIDs;
}
# if we have to deal with history states
if (
!$Preview
&& (
$Param{Restrictions}->{HistoricTimeRangeTimeNewerDate}
|| $Param{Restrictions}->{HistoricTimeRangeTimeOlderDate}
|| (
defined $Param{Restrictions}->{StateTypeIDsHistoric}
&& ref $Param{Restrictions}->{StateTypeIDsHistoric} eq 'ARRAY'
)
|| (
defined $Param{Restrictions}->{StateIDsHistoric}
&& ref $Param{Restrictions}->{StateIDsHistoric} eq 'ARRAY'
)
)
&& @TicketIDs
)
{
my $SQLTicketIDInCondition = $DBObject->QueryInCondition(
Key => 'ticket_id',
Values => \@TicketIDs,
QuoteType => 'Integer',
BindMode => 0,
);
# start building the SQL query from back to front
# what's fixed is the history_type_id we have to search for
# 1 is ticketcreate
# 27 is state update
my $SQL = $SQLTicketIDInCondition . ' AND history_type_id IN (1,27) ORDER BY ticket_id ASC';
my %StateIDs;
# if we have certain state types we have to search for
# build a hash holding all ticket StateIDs => StateNames
# we are searching for
if (
defined $Param{Restrictions}->{StateTypeIDsHistoric}
&& ref $Param{Restrictions}->{StateTypeIDsHistoric} eq 'ARRAY'
)
{
# getting the StateListType:
# my %ListType = (
# 1 => "new",
# 2 => "open",
# 3 => "closed",
# 4 => "pending reminder",
# 5 => "pending auto",
# 6 => "removed",
# 7 => "merged",
# );
my %ListType = $StateObject->StateTypeList(
UserID => 1,
);
# Takes the Array of StateTypeID's
# example: (1, 3, 5, 6, 7)
# maps the ID's to the StateTypeNames
# results in a Hash containing the StateTypeNames
# example:
# %StateTypeHash = {
# 'closed' => 1,
# 'removed' => 1,
# 'pending auto' => 1,
# 'merged' => 1,
# 'new' => 1
# };
my %StateTypeHash = map { $ListType{$_} => 1 }
@{ $Param{Restrictions}->{StateTypeIDsHistoric} };
# And now get the StatesByType
# Result is a Hash {ID => StateName,}
my @StateTypes = keys %StateTypeHash;
%StateIDs = $StateObject->StateGetStatesByType(
StateType => [ keys %StateTypeHash ],
Result => 'HASH',
);
}
# if we had certain states selected, add them to the
# StateIDs Hash
if (
defined $Param{Restrictions}->{StateIDsHistoric}
&& ref $Param{Restrictions}->{StateIDsHistoric} eq 'ARRAY'
)
{
# Validate the StateIDsHistoric list by
# checking if they are in the %StateList hash
# then taking all ValidState ID's and return a hash
# holding { StateID => Name }
my %Tmp = map { $_ => $StateList{$_} }
grep { $StateList{$_} } @{ $Param{Restrictions}->{StateIDsHistoric} };
%StateIDs = ( %StateIDs, %Tmp );
}
$SQL = 'SELECT ticket_id, state_id, create_time FROM ticket_history WHERE ' . $SQL;
$DBObject->Prepare( SQL => $SQL );
# Structure:
# Stores the last TicketState:
# TicketID => [StateID, CreateTime]
my %FoundTickets;
# fetch the result
while ( my @Row = $DBObject->FetchrowArray() ) {
if ( $Row[0] ) {
my $TicketID = $Row[0];
my $StateID = $Row[1];
$DateTimeObject->Set( String => $Row[2] );
my $RowTimeUnix = $DateTimeObject->ToEpoch();
# Entries before StartTime
if ( $RowTimeUnix < $UnixTimeStart ) {
# if the ticket was already stored
if ( $FoundTickets{$TicketID} ) {
# if the current state is in the searched states
# update the record
if ( $StateIDs{$StateID} ) {
$FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ];
}
# if it is not in the searched states
# a state change happend ->
# delete the record
else {
delete $FoundTickets{$TicketID};
}
}
# if the ticket was NOT already stored
# and the state is in the searched states
# store the record
elsif ( $StateIDs{$StateID} ) {
$FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ];
}
}
# Entries between Start and EndTime
if (
$RowTimeUnix >= $UnixTimeStart
&& $RowTimeUnix <= $UnixTimeEnd
)
{
# if we found a record
# with the searched states
# add it to the FoundTickets
if ( $StateIDs{$StateID} ) {
$FoundTickets{$TicketID} = [ $StateID, $RowTimeUnix ];
}
}
}
}
# if we had tickets that matched our query
# use them to get the details for the statistic
if (%FoundTickets) {
@TicketIDs = sort { $a <=> $b } keys %FoundTickets;
}
# if no Tickets were remaining,
# after reducing the total amount by the ones
# that had none of the searched states,
# empty @TicketIDs
else {
@TicketIDs = ();
}
}
# find out if the extended version of TicketGet is needed,
my $Extended = $Self->_ExtendedAttributesCheck(
TicketAttributes => \%TicketAttributes,
);
# find out if dynamic fields are required
my $NeedDynamicFields = 0;
DYNAMICFIELDSNEEDED:
for my $ParameterName ( sort keys %TicketAttributes ) {
if ( $ParameterName =~ m{\A DynamicField_ }xms ) {
$NeedDynamicFields = 1;
last DYNAMICFIELDSNEEDED;
}
}
my $StatsObject = $Kernel::OM->Get('Kernel::System::Stats');
# generate the ticket list
my @StatArray;
for my $TicketID (@TicketIDs) {
my @ResultRow;
my %Ticket = $TicketObject->TicketGet(
TicketID => $TicketID,
UserID => 1,
Extended => $Extended,
DynamicFields => $NeedDynamicFields,
);
# Format Ticket 'Age' param into human readable format.
$Ticket{Age} = $StatsObject->_HumanReadableAgeGet(
Age => $Ticket{Age},
);
# add the accounted time if needed
if ( $TicketAttributes{AccountedTime} ) {
$Ticket{AccountedTime} = $TicketObject->TicketAccountedTimeGet( TicketID => $TicketID );
}
# add the number of articles if needed
if ( $TicketAttributes{NumberOfArticles} ) {
$Ticket{NumberOfArticles} = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleList(
TicketID => $TicketID,
UserID => 1
);
}
$Ticket{Closed} ||= '';
$Ticket{SolutionTime} ||= '';
$Ticket{SolutionDiffInMin} ||= 0;
$Ticket{SolutionInMin} ||= 0;
$Ticket{SolutionTimeEscalation} ||= 0;
$Ticket{FirstResponse} ||= '';
$Ticket{FirstResponseDiffInMin} ||= 0;
$Ticket{FirstResponseInMin} ||= 0;
$Ticket{FirstResponseTimeEscalation} ||= 0;
$Ticket{FirstLock} ||= '';
$Ticket{UpdateTimeDestinationDate} ||= '';
$Ticket{UpdateTimeDestinationTime} ||= 0;
$Ticket{UpdateTimeWorkingTime} ||= 0;
$Ticket{UpdateTimeEscalation} ||= 0;
$Ticket{SolutionTimeDestinationDate} ||= '';
$Ticket{EscalationDestinationIn} ||= '';
$Ticket{EscalationDestinationDate} ||= '';
$Ticket{EscalationTimeWorkingTime} ||= 0;
$Ticket{NumberOfArticles} ||= 0;
for my $ParameterName ( sort keys %Ticket ) {
if ( $ParameterName =~ m{\A DynamicField_ ( [a-zA-Z\d]+ ) \z}xms ) {
# loop over the dynamic fields configured
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# skip all fields that does not match with current field name ($1)
# without the 'DynamicField_' prefix
next DYNAMICFIELD if $DynamicFieldConfig->{Name} ne $1;
# prevent unitilization errors
if ( !defined $Ticket{$ParameterName} ) {
$Ticket{$ParameterName} = '';
next DYNAMICFIELD;
}
# convert from stored keys to values for certain Dynamic Fields like
# Dropdown, Checkbox and Multiselect
my $ValueLookup = $DynamicFieldBackendObject->ValueLookup(
DynamicFieldConfig => $DynamicFieldConfig,
Key => $Ticket{$ParameterName},
);
# get field value in plain text
my $ValueStrg = $DynamicFieldBackendObject->ReadableValueRender(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $ValueLookup,
);
if ( $ValueStrg->{Value} ) {
# change raw value from ticket to a plain text value
$Ticket{$ParameterName} = $ValueStrg->{Value};
## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
if ( $DynamicFieldConfig->{Name} =~ /ProcessManagementProcessID|ProcessManagementActivityID/ ) {
my $DisplayValue = $DynamicFieldBackendObject->DisplayValueRender(
DynamicFieldConfig => $DynamicFieldConfig,
Value => $ValueStrg->{Value},
ValueMaxChars => 20,
LayoutObject => $Kernel::OM->Get('Kernel::Output::HTML::Layout'),
HTMLOutput => 0,
);
$Ticket{$ParameterName} = $DisplayValue->{Value};
}
}
}
}
}
ATTRIBUTE:
for my $Attribute ( @{$SortedAttributesRef} ) {
next ATTRIBUTE if !$TicketAttributes{$Attribute};
# convert from OTRS time zone to given time zone
if (
$Param{TimeZone}
&& $Param{TimeZone} ne Kernel::System::DateTime->OTRSTimeZoneGet()
&& $Ticket{$Attribute}
&& $Ticket{$Attribute} =~ /\A(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})\z/
)
{
$Ticket{$Attribute} = $StatsObject->_FromOTRSTimeZone(
String => $Ticket{$Attribute},
TimeZone => $Param{TimeZone},
);
$Ticket{$Attribute} .= " ($Param{TimeZone})";
}
if ( $Attribute eq 'Owner' || $Attribute eq 'Responsible' ) {
$Ticket{$Attribute} = $Kernel::OM->Get('Kernel::System::User')->UserName(
User => $Ticket{$Attribute},
NoOutOfOffice => 1,
);
}
push @ResultRow, $Ticket{$Attribute};
}
push @StatArray, \@ResultRow;
}
# use a individual sort if the sort mechanism of the TicketSearch is not usable
if ( !$OrderByIsValueOfTicketSearchSort ) {
@StatArray = $Self->_IndividualResultOrder(
StatArray => \@StatArray,
OrderBy => $OrderBy,
Sort => $Sort,
SelectedAttributes => \%TicketAttributes,
Limit => $Limit,
);
}
# add a enumeration in front of each row
if ($AddEnumeration) {
my $Counter = 0;
for my $Row (@StatArray) {
unshift @{$Row}, ++$Counter;
}
}
return @StatArray;
}
sub GetHeaderLine {
my ( $Self, %Param ) = @_;
my %SelectedAttributes = map { $_ => 1 } @{ $Param{XValue}{SelectedValues} };
my $TicketAttributes = $Self->_TicketAttributes();
my $SortedAttributesRef = $Self->_SortedAttributes();
my @HeaderLine;
# get language object
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
ATTRIBUTE:
for my $Attribute ( @{$SortedAttributesRef} ) {
next ATTRIBUTE if !$SelectedAttributes{$Attribute};
push @HeaderLine, $LanguageObject->Translate( $TicketAttributes->{$Attribute} );
}
return \@HeaderLine;
}
sub ExportWrapper {
my ( $Self, %Param ) = @_;
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
# wrap ids to used spelling
for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) {
ELEMENT:
for my $Element ( @{ $Param{$Use} } ) {
next ELEMENT if !$Element || !$Element->{SelectedValues};
my $ElementName = $Element->{Element};
my $Values = $Element->{SelectedValues};
if ( $ElementName eq 'QueueIDs' || $ElementName eq 'CreatedQueueIDs' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
$ID->{Content} = $QueueObject->QueueLookup( QueueID => $ID->{Content} );
}
}
elsif ( $ElementName eq 'StateIDs' || $ElementName eq 'CreatedStateIDs' ) {
my %StateList = $StateObject->StateList( UserID => 1 );
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
$ID->{Content} = $StateList{ $ID->{Content} };
}
}
elsif ( $ElementName eq 'PriorityIDs' || $ElementName eq 'CreatedPriorityIDs' ) {
my %PriorityList = $PriorityObject->PriorityList( UserID => 1 );
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
$ID->{Content} = $PriorityList{ $ID->{Content} };
}
}
elsif (
$ElementName eq 'OwnerIDs'
|| $ElementName eq 'CreatedUserIDs'
|| $ElementName eq 'ResponsibleIDs'
)
{
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
$ID->{Content} = $UserObject->UserLookup( UserID => $ID->{Content} );
}
}
# Locks and statustype don't have to wrap because they are never different
}
}
return \%Param;
}
sub ImportWrapper {
my ( $Self, %Param ) = @_;
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
# wrap used spelling to ids
for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) {
ELEMENT:
for my $Element ( @{ $Param{$Use} } ) {
next ELEMENT if !$Element || !$Element->{SelectedValues};
my $ElementName = $Element->{Element};
my $Values = $Element->{SelectedValues};
if ( $ElementName eq 'QueueIDs' || $ElementName eq 'CreatedQueueIDs' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
if ( $QueueObject->QueueLookup( Queue => $ID->{Content} ) ) {
$ID->{Content} = $QueueObject->QueueLookup( Queue => $ID->{Content} );
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Import: Can' find the queue $ID->{Content}!"
);
$ID = undef;
}
}
}
elsif ( $ElementName eq 'StateIDs' || $ElementName eq 'CreatedStateIDs' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my %State = $StateObject->StateGet(
Name => $ID->{Content},
Cache => 1,
);
if ( $State{ID} ) {
$ID->{Content} = $State{ID};
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Import: Can' find state $ID->{Content}!"
);
$ID = undef;
}
}
}
elsif ( $ElementName eq 'PriorityIDs' || $ElementName eq 'CreatedPriorityIDs' ) {
my %PriorityList = $PriorityObject->PriorityList( UserID => 1 );
my %PriorityIDs;
for my $Key ( sort keys %PriorityList ) {
$PriorityIDs{ $PriorityList{$Key} } = $Key;
}
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
if ( $PriorityIDs{ $ID->{Content} } ) {
$ID->{Content} = $PriorityIDs{ $ID->{Content} };
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Import: Can' find priority $ID->{Content}!"
);
$ID = undef;
}
}
}
elsif (
$ElementName eq 'OwnerIDs'
|| $ElementName eq 'CreatedUserIDs'
|| $ElementName eq 'ResponsibleIDs'
)
{
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
if ( $UserObject->UserLookup( UserLogin => $ID->{Content} ) ) {
$ID->{Content} = $UserObject->UserLookup(
UserLogin => $ID->{Content}
);
}
else {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Import: Can' find user $ID->{Content}!"
);
$ID = undef;
}
}
}
# Locks and statustype don't have to wrap because they are never different
}
}
return \%Param;
}
sub _TicketAttributes {
my $Self = shift;
# get config object
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
my %TicketAttributes = (
Number => Translatable('Number'), # only a counter for a better readability
TicketNumber => $ConfigObject->Get('Ticket::Hook'),
#TicketID => 'TicketID',
Age => 'Age',
Title => 'Title',
Queue => 'Queue',
#QueueID => 'QueueID',
State => 'State',
#StateID => 'StateID',
Priority => 'Priority',
#PriorityID => 'PriorityID',
CustomerID => 'Customer ID',
Changed => Translatable('Last Changed'),
Created => 'Created',
CustomerUserID => 'Customer User',
Lock => 'Lock',
#LockID => 'LockID',
UnlockTimeout => 'UnlockTimeout',
AccountedTime => 'Accounted time', # the same wording is in AgentTicketPrint.tt
RealTillTimeNotUsed => 'RealTillTimeNotUsed',
NumberOfArticles => 'Number of Articles',
#GroupID => 'GroupID',
StateType => 'StateType',
UntilTime => 'UntilTime',
Closed => 'Close Time',
FirstLock => 'First Lock',
EscalationResponseTime => 'EscalationResponseTime',
EscalationUpdateTime => 'EscalationUpdateTime',
EscalationSolutionTime => 'EscalationSolutionTime',
EscalationDestinationIn => 'EscalationDestinationIn',
# EscalationDestinationTime => 'EscalationDestinationTime',
EscalationDestinationDate => 'EscalationDestinationDate',
EscalationTimeWorkingTime => 'EscalationTimeWorkingTime',
EscalationTime => 'EscalationTime',
FirstResponse => 'FirstResponse',
FirstResponseInMin => 'FirstResponseInMin',
FirstResponseDiffInMin => 'FirstResponseDiffInMin',
FirstResponseTimeWorkingTime => 'FirstResponseTimeWorkingTime',
FirstResponseTimeEscalation => 'FirstResponseTimeEscalation',
FirstResponseTimeNotification => 'FirstResponseTimeNotification',
FirstResponseTimeDestinationTime => 'FirstResponseTimeDestinationTime',
FirstResponseTimeDestinationDate => 'FirstResponseTimeDestinationDate',
FirstResponseTime => 'FirstResponseTime',
UpdateTimeEscalation => 'UpdateTimeEscalation',
UpdateTimeNotification => 'UpdateTimeNotification',
UpdateTimeDestinationTime => 'UpdateTimeDestinationTime',
UpdateTimeDestinationDate => 'UpdateTimeDestinationDate',
UpdateTimeWorkingTime => 'UpdateTimeWorkingTime',
UpdateTime => 'UpdateTime',
SolutionTime => 'SolutionTime',
SolutionInMin => 'SolutionInMin',
SolutionDiffInMin => 'SolutionDiffInMin',
SolutionTimeWorkingTime => 'SolutionTimeWorkingTime',
SolutionTimeEscalation => 'SolutionTimeEscalation',
SolutionTimeNotification => 'SolutionTimeNotification',
SolutionTimeDestinationTime => 'SolutionTimeDestinationTime',
SolutionTimeDestinationDate => 'SolutionTimeDestinationDate',
SolutionTimeWorkingTime => 'SolutionTimeWorkingTime',
);
if ( $ConfigObject->Get('Ticket::Service') ) {
$TicketAttributes{Service} = 'Service';
#$TicketAttributes{ServiceID} = 'ServiceID',
$TicketAttributes{SLA} = 'SLA';
$TicketAttributes{SLAID} = 'SLAID';
}
if ( $ConfigObject->Get('Ticket::Type') ) {
$TicketAttributes{Type} = 'Type';
#$TicketAttributes{TypeID} = 'TypeID';
}
if ( $ConfigObject->Get('Stats::UseAgentElementInStats') ) {
$TicketAttributes{Owner} = 'Agent/Owner';
#$TicketAttributes{OwnerID} = 'OwnerID';
# ? $TicketAttributes{CreatedUserIDs} = 'Created by Agent/Owner';
$TicketAttributes{Responsible} = 'Responsible';
#$TicketAttributes{ResponsibleID} = 'ResponsibleID';
}
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
$TicketAttributes{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $DynamicFieldConfig->{Label};
}
return \%TicketAttributes;
}
sub _SortedAttributes {
my $Self = shift;
my @SortedAttributes = qw(
Number
TicketNumber
Age
Title
Created
Changed
Closed
Queue
State
Priority
CustomerUserID
CustomerID
Service
SLA
Type
Owner
Responsible
AccountedTime
EscalationDestinationIn
EscalationDestinationTime
EscalationDestinationDate
EscalationTimeWorkingTime
EscalationTime
FirstResponse
FirstResponseInMin
FirstResponseDiffInMin
FirstResponseTimeWorkingTime
FirstResponseTimeEscalation
FirstResponseTimeNotification
FirstResponseTimeDestinationTime
FirstResponseTimeDestinationDate
FirstResponseTime
UpdateTimeEscalation
UpdateTimeNotification
UpdateTimeDestinationTime
UpdateTimeDestinationDate
UpdateTimeWorkingTime
UpdateTime
SolutionTime
SolutionInMin
SolutionDiffInMin
SolutionTimeWorkingTime
SolutionTimeEscalation
SolutionTimeNotification
SolutionTimeDestinationTime
SolutionTimeDestinationDate
SolutionTimeWorkingTime
FirstLock
Lock
StateType
UntilTime
UnlockTimeout
EscalationResponseTime
EscalationSolutionTime
EscalationUpdateTime
RealTillTimeNotUsed
NumberOfArticles
);
# cycle trought the Dynamic Fields
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# add dynamic field attribute
push @SortedAttributes, 'DynamicField_' . $DynamicFieldConfig->{Name};
}
return \@SortedAttributes;
}
sub _ExtendedAttributesCheck {
my ( $Self, %Param ) = @_;
my @ExtendedAttributes = qw(
FirstResponse
FirstResponseInMin
FirstResponseDiffInMin
FirstResponseTimeWorkingTime
Closed
SolutionTime
SolutionInMin
SolutionDiffInMin
SolutionTimeWorkingTime
FirstLock
);
ATTRIBUTE:
for my $Attribute (@ExtendedAttributes) {
return 1 if $Param{TicketAttributes}{$Attribute};
}
return;
}
sub _OrderByIsValueOfTicketSearchSort {
my ( $Self, %Param ) = @_;
my %SortOptions = (
Age => 'Age',
Created => 'Age',
CustomerID => 'CustomerID',
EscalationResponseTime => 'EscalationResponseTime',
EscalationSolutionTime => 'EscalationSolutionTime',
EscalationTime => 'EscalationTime',
EscalationUpdateTime => 'EscalationUpdateTime',
Lock => 'Lock',
Owner => 'Owner',
Priority => 'Priority',
Queue => 'Queue',
Responsible => 'Responsible',
SLA => 'SLA',
Service => 'Service',
State => 'State',
TicketNumber => 'Ticket',
TicketEscalation => 'TicketEscalation',
Title => 'Title',
Type => 'Type',
);
# get dynamic field backend object
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
# cycle trought the Dynamic Fields
DYNAMICFIELD:
for my $DynamicFieldConfig ( @{ $Self->{DynamicField} } ) {
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
# get dynamic field sortable condition
my $IsSortable = $DynamicFieldBackendObject->HasBehavior(
DynamicFieldConfig => $DynamicFieldConfig,
Behavior => 'IsSortable',
);
# add dynamic field if is sortable
if ($IsSortable) {
$SortOptions{ 'DynamicField_' . $DynamicFieldConfig->{Name} }
= 'DynamicField_' . $DynamicFieldConfig->{Name};
}
}
return $SortOptions{ $Param{OrderBy} } if $SortOptions{ $Param{OrderBy} };
return;
}
sub _IndividualResultOrder {
my ( $Self, %Param ) = @_;
my @Unsorted = @{ $Param{StatArray} };
my @Sorted;
# find out the positon of the values which should be
# used for the order
my $Counter = 0;
my $SortedAttributes = $Self->_SortedAttributes();
ATTRIBUTE:
for my $Attribute ( @{$SortedAttributes} ) {
next ATTRIBUTE if !$Param{SelectedAttributes}{$Attribute};
last ATTRIBUTE if $Attribute eq $Param{OrderBy};
$Counter++;
}
# order after a individual attribute
if ( $Param{OrderBy} eq 'AccountedTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'SolutionTime' ) {
@Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'SolutionDiffInMin' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'SolutionInMin' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'SolutionTimeWorkingTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'FirstResponse' ) {
@Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'FirstResponseDiffInMin' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'FirstResponseInMin' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'FirstResponseTimeWorkingTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'FirstLock' ) {
@Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'StateType' ) {
@Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'UntilTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'UnlockTimeout' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'EscalationResponseTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'EscalationUpdateTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'EscalationSolutionTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'RealTillTimeNotUsed' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'EscalationTimeWorkingTime' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
elsif ( $Param{OrderBy} eq 'NumberOfArticles' ) {
@Sorted = sort { $a->[$Counter] <=> $b->[$Counter] } @Unsorted;
}
else {
@Sorted = sort { $a->[$Counter] cmp $b->[$Counter] } @Unsorted;
}
# make a reverse sort if needed
if ( $Param{Sort} eq 'Down' ) {
@Sorted = reverse @Sorted;
}
# take care about the limit
if ( $Param{Limit} && $Param{Limit} ne 'unlimited' ) {
my $Count = 0;
@Sorted = grep { ++$Count <= $Param{Limit} } @Sorted;
}
return @Sorted;
}
1;