7863 lines
218 KiB
Perl
7863 lines
218 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::Ticket;
|
||
|
||
use strict;
|
||
use warnings;
|
||
|
||
use File::Path;
|
||
use utf8;
|
||
use Encode ();
|
||
|
||
use parent qw(
|
||
Kernel::System::EventHandler
|
||
Kernel::System::Ticket::TicketSearch
|
||
Kernel::System::Ticket::TicketACL
|
||
);
|
||
|
||
use Kernel::Language qw(Translatable);
|
||
use Kernel::System::VariableCheck qw(:all);
|
||
|
||
our @ObjectDependencies = (
|
||
'Kernel::Config',
|
||
'Kernel::System::Cache',
|
||
'Kernel::System::Calendar',
|
||
'Kernel::System::CustomerUser',
|
||
'Kernel::System::DB',
|
||
'Kernel::System::DynamicField',
|
||
'Kernel::System::DynamicField::Backend',
|
||
'Kernel::System::DynamicFieldValue',
|
||
'Kernel::System::Email',
|
||
'Kernel::System::Group',
|
||
'Kernel::System::HTMLUtils',
|
||
'Kernel::System::LinkObject',
|
||
'Kernel::System::Lock',
|
||
'Kernel::System::Log',
|
||
'Kernel::System::Main',
|
||
'Kernel::System::PostMaster::LoopProtection',
|
||
'Kernel::System::Priority',
|
||
'Kernel::System::Queue',
|
||
'Kernel::System::Service',
|
||
'Kernel::System::SLA',
|
||
'Kernel::System::State',
|
||
'Kernel::System::TemplateGenerator',
|
||
'Kernel::System::DateTime',
|
||
'Kernel::System::Ticket::Article',
|
||
'Kernel::System::Type',
|
||
'Kernel::System::User',
|
||
'Kernel::System::Valid',
|
||
'Kernel::Language',
|
||
);
|
||
|
||
=head1 NAME
|
||
|
||
Kernel::System::Ticket - Functions to create, modify and delete tickets as well as related helper functions
|
||
|
||
=head1 SYNOPSIS
|
||
|
||
Create ticket object
|
||
|
||
use Kernel::System::ObjectManager;
|
||
local $Kernel::OM = Kernel::System::ObjectManager->new();
|
||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||
|
||
Create a new ticket
|
||
|
||
my $TicketID = $TicketObject->TicketCreate(
|
||
Title => 'Some Ticket Title',
|
||
Queue => 'Raw',
|
||
Lock => 'unlock',
|
||
Priority => '3 normal',
|
||
State => 'new',
|
||
CustomerID => '12345',
|
||
CustomerUser => 'customer@example.com',
|
||
OwnerID => 1,
|
||
UserID => 1,
|
||
);
|
||
|
||
Lock the ticket
|
||
|
||
my $Success = $TicketObject->TicketLockSet(
|
||
Lock => 'lock',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
|
||
Update the title
|
||
|
||
my $Success = $TicketObject->TicketTitleUpdate(
|
||
Title => 'Some Title',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Move ticket to another queue
|
||
|
||
my $Success = $TicketObject->TicketQueueSet(
|
||
Queue => 'Some Queue Name',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Set a ticket type
|
||
|
||
my $Success = $TicketObject->TicketTypeSet(
|
||
Type => 'Incident',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Assign another customer
|
||
|
||
my $Success = $TicketObject->TicketCustomerSet(
|
||
No => '12345',
|
||
User => 'customer@company.org',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Update the state
|
||
|
||
my $Success = $TicketObject->TicketStateSet(
|
||
State => 'pending reminder',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Update pending time (only for pending states)
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
String => '2019-08-14 22:05:00',
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Set a new priority
|
||
|
||
my $Success = $TicketObject->TicketPrioritySet(
|
||
TicketID => $TicketID,
|
||
Priority => 'low',
|
||
UserID => 1,
|
||
);
|
||
|
||
Assign to another agent
|
||
|
||
my $Success = $TicketObject->TicketOwnerSet(
|
||
TicketID => $TicketID,
|
||
NewUserID => 2,
|
||
UserID => 1,
|
||
);
|
||
|
||
Set a responsible
|
||
|
||
my $Success = $TicketObject->TicketResponsibleSet(
|
||
TicketID => $TicketID,
|
||
NewUserID => 3,
|
||
UserID => 1,
|
||
);
|
||
|
||
Add something to the history
|
||
|
||
my $Success = $TicketObject->HistoryAdd(
|
||
Name => 'Some Comment',
|
||
HistoryType => 'Move',
|
||
TicketID => $TicketID,
|
||
CreateUserID => 1,
|
||
);
|
||
|
||
Get the complete ticket history
|
||
|
||
my @HistoryLines = $TicketObject->HistoryGet(
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Get current ticket attributes
|
||
|
||
my %Ticket = $TicketObject->TicketGet(
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
Delete the ticket
|
||
|
||
my $Success = $TicketObject->TicketDelete(
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
=head1 PUBLIC INTERFACE
|
||
|
||
=head2 new()
|
||
|
||
Don't use the constructor directly, use the ObjectManager instead:
|
||
|
||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||
|
||
=cut
|
||
|
||
sub new {
|
||
my ( $Type, %Param ) = @_;
|
||
|
||
# allocate new hash for object
|
||
my $Self = {};
|
||
bless( $Self, $Type );
|
||
|
||
# 0=off; 1=on;
|
||
$Self->{Debug} = $Param{Debug} || 0;
|
||
|
||
$Self->{CacheType} = 'Ticket';
|
||
$Self->{CacheTTL} = 60 * 60 * 24 * 20;
|
||
|
||
# init of event handler
|
||
$Self->EventHandlerInit(
|
||
Config => 'Ticket::EventModulePost',
|
||
);
|
||
|
||
# get needed objects
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
||
|
||
# load ticket extension modules
|
||
my $CustomModule = $ConfigObject->Get('Ticket::CustomModule');
|
||
if ($CustomModule) {
|
||
|
||
my %ModuleList;
|
||
if ( ref $CustomModule eq 'HASH' ) {
|
||
%ModuleList = %{$CustomModule};
|
||
}
|
||
else {
|
||
$ModuleList{Init} = $CustomModule;
|
||
}
|
||
|
||
MODULEKEY:
|
||
for my $ModuleKey ( sort keys %ModuleList ) {
|
||
|
||
my $Module = $ModuleList{$ModuleKey};
|
||
|
||
next MODULEKEY if !$Module;
|
||
next MODULEKEY if !$MainObject->RequireBaseClass($Module);
|
||
}
|
||
}
|
||
|
||
return $Self;
|
||
}
|
||
|
||
=head2 TicketCreateNumber()
|
||
|
||
creates a new ticket number
|
||
|
||
my $TicketNumber = $TicketObject->TicketCreateNumber();
|
||
|
||
=cut
|
||
|
||
sub TicketCreateNumber {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator')
|
||
|| 'Kernel::System::Ticket::Number::AutoIncrement';
|
||
|
||
return $Kernel::OM->Get($GeneratorModule)->TicketCreateNumber(%Param);
|
||
}
|
||
|
||
=head2 GetTNByString()
|
||
|
||
creates a new ticket number
|
||
|
||
my $TicketNumber = $TicketObject->GetTNByString($Subject);
|
||
|
||
=cut
|
||
|
||
sub GetTNByString {
|
||
my ( $Self, $String ) = @_;
|
||
|
||
my $GeneratorModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::NumberGenerator')
|
||
|| 'Kernel::System::Ticket::Number::AutoIncrement';
|
||
|
||
return $Kernel::OM->Get($GeneratorModule)->GetTNByString($String);
|
||
}
|
||
|
||
=head2 TicketCheckNumber()
|
||
|
||
checks if ticket number exists, returns ticket id if number exists.
|
||
|
||
returns the merged ticket id if ticket was merged.
|
||
only into a depth of maximum 10 merges
|
||
|
||
my $TicketID = $TicketObject->TicketCheckNumber(
|
||
Tn => '200404051004575',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketCheckNumber {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{Tn} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TN!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT id FROM ticket WHERE tn = ?',
|
||
Bind => [ \$Param{Tn} ],
|
||
Limit => 1,
|
||
);
|
||
|
||
my $TicketID;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$TicketID = $Row[0];
|
||
}
|
||
|
||
# get main ticket id if ticket has been merged
|
||
return if !$TicketID;
|
||
|
||
# do not check deeper than 10 merges
|
||
my $Limit = 10;
|
||
my $Count = 1;
|
||
MERGELOOP:
|
||
for ( 1 .. $Limit ) {
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $TicketID,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
return $TicketID if $Ticket{StateType} ne 'merged';
|
||
|
||
# get ticket history
|
||
my @Lines = $Self->HistoryGet(
|
||
TicketID => $TicketID,
|
||
UserID => 1,
|
||
);
|
||
|
||
HISTORYLINE:
|
||
for my $Data ( reverse @Lines ) {
|
||
next HISTORYLINE if $Data->{HistoryType} ne 'Merged';
|
||
if ( $Data->{Name} =~ /^.*%%\d+?%%(\d+?)$/ ) {
|
||
$TicketID = $1;
|
||
$Count++;
|
||
next MERGELOOP if ( $Count <= $Limit );
|
||
|
||
# returns no found Ticket after 10 deep-merges, so it should create a new one
|
||
return;
|
||
}
|
||
}
|
||
|
||
return $TicketID;
|
||
}
|
||
return;
|
||
}
|
||
|
||
=head2 TicketCreate()
|
||
|
||
creates a new ticket
|
||
|
||
my $TicketID = $TicketObject->TicketCreate(
|
||
Title => 'Some Ticket Title',
|
||
Queue => 'Raw', # or QueueID => 123,
|
||
Lock => 'unlock',
|
||
Priority => '3 normal', # or PriorityID => 2,
|
||
State => 'new', # or StateID => 5,
|
||
CustomerID => '123465',
|
||
CustomerUser => 'customer@example.com',
|
||
OwnerID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
or
|
||
|
||
my $TicketID = $TicketObject->TicketCreate(
|
||
TN => $TicketObject->TicketCreateNumber(), # optional
|
||
Title => 'Some Ticket Title',
|
||
Queue => 'Raw', # or QueueID => 123,
|
||
Lock => 'unlock',
|
||
Priority => '3 normal', # or PriorityID => 2,
|
||
State => 'new', # or StateID => 5,
|
||
Type => 'Incident', # or TypeID = 1 or Ticket type default (Ticket::Type::Default), not required
|
||
Service => 'Service A', # or ServiceID => 1, not required
|
||
SLA => 'SLA A', # or SLAID => 1, not required
|
||
CustomerID => '123465',
|
||
CustomerUser => 'customer@example.com',
|
||
OwnerID => 123,
|
||
ResponsibleID => 123, # not required
|
||
ArchiveFlag => 'y', # (y|n) not required
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketCreate
|
||
|
||
=cut
|
||
|
||
sub TicketCreate {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(OwnerID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
my $ArchiveFlag = 0;
|
||
if ( $Param{ArchiveFlag} && $Param{ArchiveFlag} eq 'y' ) {
|
||
$ArchiveFlag = 1;
|
||
}
|
||
|
||
$Param{ResponsibleID} ||= 1;
|
||
|
||
# get type object
|
||
my $TypeObject = $Kernel::OM->Get('Kernel::System::Type');
|
||
|
||
if ( !$Param{TypeID} && !$Param{Type} ) {
|
||
|
||
# get default ticket type
|
||
my $DefaultTicketType = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type::Default');
|
||
|
||
# check if default ticket type exists
|
||
my %AllTicketTypes = reverse $TypeObject->TypeList();
|
||
|
||
if ( $AllTicketTypes{$DefaultTicketType} ) {
|
||
$Param{Type} = $DefaultTicketType;
|
||
}
|
||
else {
|
||
$Param{TypeID} = 1;
|
||
}
|
||
}
|
||
|
||
# TypeID/Type lookup!
|
||
if ( !$Param{TypeID} && $Param{Type} ) {
|
||
$Param{TypeID} = $TypeObject->TypeLookup( Type => $Param{Type} );
|
||
}
|
||
elsif ( $Param{TypeID} && !$Param{Type} ) {
|
||
$Param{Type} = $TypeObject->TypeLookup( TypeID => $Param{TypeID} );
|
||
}
|
||
if ( !$Param{TypeID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No TypeID for '$Param{Type}'!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get queue object
|
||
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
|
||
|
||
# QueueID/Queue lookup!
|
||
if ( !$Param{QueueID} && $Param{Queue} ) {
|
||
$Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} );
|
||
}
|
||
elsif ( !$Param{Queue} ) {
|
||
$Param{Queue} = $QueueObject->QueueLookup( QueueID => $Param{QueueID} );
|
||
}
|
||
if ( !$Param{QueueID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No QueueID for '$Param{Queue}'!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get state object
|
||
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
|
||
|
||
# StateID/State lookup!
|
||
if ( !$Param{StateID} ) {
|
||
my %State = $StateObject->StateGet( Name => $Param{State} );
|
||
$Param{StateID} = $State{ID};
|
||
}
|
||
elsif ( !$Param{State} ) {
|
||
my %State = $StateObject->StateGet( ID => $Param{StateID} );
|
||
$Param{State} = $State{Name};
|
||
}
|
||
if ( !$Param{StateID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No StateID for '$Param{State}'!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# LockID lookup!
|
||
if ( !$Param{LockID} && $Param{Lock} ) {
|
||
|
||
$Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
|
||
Lock => $Param{Lock},
|
||
);
|
||
}
|
||
if ( !$Param{LockID} && !$Param{Lock} ) {
|
||
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'No LockID and no LockType!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get priority object
|
||
my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
|
||
|
||
# PriorityID/Priority lookup!
|
||
if ( !$Param{PriorityID} && $Param{Priority} ) {
|
||
$Param{PriorityID} = $PriorityObject->PriorityLookup(
|
||
Priority => $Param{Priority},
|
||
);
|
||
}
|
||
elsif ( $Param{PriorityID} && !$Param{Priority} ) {
|
||
$Param{Priority} = $PriorityObject->PriorityLookup(
|
||
PriorityID => $Param{PriorityID},
|
||
);
|
||
}
|
||
if ( !$Param{PriorityID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'No PriorityID (invalid Priority Name?)!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get service object
|
||
my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service');
|
||
|
||
# ServiceID/Service lookup!
|
||
if ( !$Param{ServiceID} && $Param{Service} ) {
|
||
$Param{ServiceID} = $ServiceObject->ServiceLookup(
|
||
Name => $Param{Service},
|
||
);
|
||
}
|
||
elsif ( $Param{ServiceID} && !$Param{Service} ) {
|
||
$Param{Service} = $ServiceObject->ServiceLookup(
|
||
ServiceID => $Param{ServiceID},
|
||
);
|
||
}
|
||
|
||
# get sla object
|
||
my $SLAObject = $Kernel::OM->Get('Kernel::System::SLA');
|
||
|
||
# SLAID/SLA lookup!
|
||
if ( !$Param{SLAID} && $Param{SLA} ) {
|
||
$Param{SLAID} = $SLAObject->SLALookup( Name => $Param{SLA} );
|
||
}
|
||
elsif ( $Param{SLAID} && !$Param{SLA} ) {
|
||
$Param{SLA} = $SLAObject->SLALookup( SLAID => $Param{SLAID} );
|
||
}
|
||
|
||
# create ticket number if none is given
|
||
if ( !$Param{TN} ) {
|
||
$Param{TN} = $Self->TicketCreateNumber();
|
||
}
|
||
|
||
# check ticket title
|
||
if ( !defined $Param{Title} ) {
|
||
$Param{Title} = '';
|
||
}
|
||
|
||
# substitute title if needed
|
||
else {
|
||
$Param{Title} = substr( $Param{Title}, 0, 255 );
|
||
}
|
||
|
||
# check database undef/NULL (set value to undef/NULL to prevent database errors)
|
||
$Param{ServiceID} ||= undef;
|
||
$Param{SLAID} ||= undef;
|
||
|
||
# create db record
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => '
|
||
INSERT INTO ticket (tn, title, type_id, queue_id, ticket_lock_id,
|
||
user_id, responsible_user_id, ticket_priority_id, ticket_state_id,
|
||
escalation_time, escalation_update_time, escalation_response_time,
|
||
escalation_solution_time, timeout, service_id, sla_id, until_time,
|
||
archive_flag, create_time, create_by, change_time, change_by)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0, 0, 0, ?, ?, 0, ?,
|
||
current_timestamp, ?, current_timestamp, ?)',
|
||
Bind => [
|
||
\$Param{TN}, \$Param{Title}, \$Param{TypeID}, \$Param{QueueID},
|
||
\$Param{LockID}, \$Param{OwnerID}, \$Param{ResponsibleID},
|
||
\$Param{PriorityID}, \$Param{StateID}, \$Param{ServiceID},
|
||
\$Param{SLAID}, \$ArchiveFlag, \$Param{UserID}, \$Param{UserID},
|
||
],
|
||
);
|
||
|
||
# get ticket id
|
||
my $TicketID = $Self->TicketIDLookup(
|
||
TicketNumber => $Param{TN},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# add history entry
|
||
$Self->HistoryAdd(
|
||
TicketID => $TicketID,
|
||
QueueID => $Param{QueueID},
|
||
HistoryType => 'NewTicket',
|
||
Name => "\%\%$Param{TN}\%\%$Param{Queue}\%\%$Param{Priority}\%\%$Param{State}\%\%$TicketID",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') ) {
|
||
|
||
# history insert for service so that initial values can be seen
|
||
my $HistoryService = $Param{Service} || 'NULL';
|
||
my $HistoryServiceID = $Param{ServiceID} || '';
|
||
$Self->HistoryAdd(
|
||
TicketID => $TicketID,
|
||
HistoryType => 'ServiceUpdate',
|
||
Name => "\%\%$HistoryService\%\%$HistoryServiceID\%\%NULL\%\%",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# history insert for SLA
|
||
my $HistorySLA = $Param{SLA} || 'NULL';
|
||
my $HistorySLAID = $Param{SLAID} || '';
|
||
$Self->HistoryAdd(
|
||
TicketID => $TicketID,
|
||
HistoryType => 'SLAUpdate',
|
||
Name => "\%\%$HistorySLA\%\%$HistorySLAID\%\%NULL\%\%",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Type') ) {
|
||
|
||
# Insert history record for ticket type, so that initial value can be seen.
|
||
# Please see bug#12702 for more information.
|
||
$Self->HistoryAdd(
|
||
TicketID => $TicketID,
|
||
HistoryType => 'TypeUpdate',
|
||
Name => "\%\%$Param{Type}\%\%$Param{TypeID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# set customer data if given
|
||
if ( $Param{CustomerNo} || $Param{CustomerID} || $Param{CustomerUser} ) {
|
||
$Self->TicketCustomerSet(
|
||
TicketID => $TicketID,
|
||
No => $Param{CustomerNo} || $Param{CustomerID} || '',
|
||
User => $Param{CustomerUser} || '',
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# update ticket view index
|
||
$Self->TicketAcceleratorAdd( TicketID => $TicketID );
|
||
|
||
# log ticket creation
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'info',
|
||
Message => "New Ticket [$Param{TN}/" . substr( $Param{Title}, 0, 15 ) . "] created "
|
||
. "(TicketID=$TicketID,Queue=$Param{Queue},Priority=$Param{Priority},State=$Param{State})",
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketCreate',
|
||
Data => {
|
||
TicketID => $TicketID,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return $TicketID;
|
||
}
|
||
|
||
=head2 TicketDelete()
|
||
|
||
deletes a ticket with articles from storage
|
||
|
||
my $Success = $TicketObject->TicketDelete(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketDelete
|
||
|
||
=cut
|
||
|
||
sub TicketDelete {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# Delete dynamic field values for this ticket.
|
||
$Kernel::OM->Get('Kernel::System::DynamicFieldValue')->ObjectValuesDelete(
|
||
ObjectType => 'Ticket',
|
||
ObjectID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# delete ticket links
|
||
$Kernel::OM->Get('Kernel::System::LinkObject')->LinkDeleteAll(
|
||
Object => 'Ticket',
|
||
Key => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# update ticket index
|
||
return if !$Self->TicketAcceleratorDelete(%Param);
|
||
|
||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||
|
||
# delete ticket entries from article search index.
|
||
return if !$ArticleObject->ArticleSearchIndexDelete(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# remove ticket watcher
|
||
return if !$DBObject->Do(
|
||
SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# delete ticket flags
|
||
return if !$DBObject->Do(
|
||
SQL => 'DELETE FROM ticket_flag WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# delete ticket flag cache
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
);
|
||
|
||
# Delete calendar appointments linked to this ticket.
|
||
# Please see bug#13642 for more information.
|
||
return if !$Kernel::OM->Get('Kernel::System::Calendar')->TicketAppointmentDelete(
|
||
TicketID => $Param{TicketID},
|
||
);
|
||
|
||
# delete ticket_history
|
||
return if !$Self->HistoryDelete(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# Delete all articles and associated data.
|
||
my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} );
|
||
for my $MetaArticle (@Articles) {
|
||
return if !$ArticleObject->BackendForArticle( %{$MetaArticle} )->ArticleDelete(
|
||
ArticleID => $MetaArticle->{ArticleID},
|
||
%Param,
|
||
);
|
||
}
|
||
|
||
# delete ticket
|
||
return if !$DBObject->Do(
|
||
SQL => 'DELETE FROM ticket WHERE id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketDelete',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# Clear ticket cache again, in case it was rebuilt in the meantime.
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketIDLookup()
|
||
|
||
ticket id lookup by ticket number
|
||
|
||
my $TicketID = $TicketObject->TicketIDLookup(
|
||
TicketNumber => '2004040510440485',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketIDLookup {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketNumber} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketNumber!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT id FROM ticket WHERE tn = ?',
|
||
Bind => [ \$Param{TicketNumber} ],
|
||
Limit => 1,
|
||
);
|
||
|
||
my $ID;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$ID = $Row[0];
|
||
}
|
||
|
||
return $ID;
|
||
}
|
||
|
||
=head2 TicketNumberLookup()
|
||
|
||
ticket number lookup by ticket id
|
||
|
||
my $TicketNumber = $TicketObject->TicketNumberLookup(
|
||
TicketID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketNumberLookup {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT tn FROM ticket WHERE id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
Limit => 1,
|
||
);
|
||
|
||
my $Number;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Number = $Row[0];
|
||
}
|
||
|
||
return $Number;
|
||
}
|
||
|
||
=head2 TicketSubjectBuild()
|
||
|
||
rebuild a new ticket subject
|
||
|
||
This will generate a subject like C<RE: [Ticket# 2004040510440485] Some subject>
|
||
|
||
my $NewSubject = $TicketObject->TicketSubjectBuild(
|
||
TicketNumber => '2004040510440485',
|
||
Subject => $OldSubject,
|
||
Action => 'Reply',
|
||
);
|
||
|
||
This will generate a subject like C<[Ticket# 2004040510440485] Some subject>
|
||
(so without RE: )
|
||
|
||
my $NewSubject = $TicketObject->TicketSubjectBuild(
|
||
TicketNumber => '2004040510440485',
|
||
Subject => $OldSubject,
|
||
Type => 'New',
|
||
Action => 'Reply',
|
||
);
|
||
|
||
This will generate a subject like C<FWD: [Ticket# 2004040510440485] Some subject>
|
||
|
||
my $NewSubject = $TicketObject->TicketSubjectBuild(
|
||
TicketNumber => '2004040510440485',
|
||
Subject => $OldSubject,
|
||
Action => 'Forward', # Possible values are Reply and Forward, Reply is default.
|
||
);
|
||
|
||
This will generate a subject like C<[Ticket# 2004040510440485] Re: Some subject>
|
||
(so without clean-up of subject)
|
||
|
||
my $NewSubject = $TicketObject->TicketSubjectBuild(
|
||
TicketNumber => '2004040510440485',
|
||
Subject => $OldSubject,
|
||
Type => 'New',
|
||
NoCleanup => 1,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketSubjectBuild {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !defined $Param{TicketNumber} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketNumber!"
|
||
);
|
||
return;
|
||
}
|
||
my $Subject = $Param{Subject} || '';
|
||
my $Action = $Param{Action} || 'Reply';
|
||
|
||
# cleanup of subject, remove existing ticket numbers and reply indentifier
|
||
if ( !$Param{NoCleanup} ) {
|
||
$Subject = $Self->TicketSubjectClean(%Param);
|
||
}
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# get config options
|
||
my $TicketHook = $ConfigObject->Get('Ticket::Hook');
|
||
my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider');
|
||
my $TicketSubjectRe = $ConfigObject->Get('Ticket::SubjectRe');
|
||
my $TicketSubjectFwd = $ConfigObject->Get('Ticket::SubjectFwd');
|
||
my $TicketSubjectFormat = $ConfigObject->Get('Ticket::SubjectFormat') || 'Left';
|
||
|
||
# return subject for new tickets
|
||
if ( $Param{Type} && $Param{Type} eq 'New' ) {
|
||
if ( lc $TicketSubjectFormat eq 'right' ) {
|
||
return $Subject . " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
|
||
}
|
||
if ( lc $TicketSubjectFormat eq 'none' ) {
|
||
return $Subject;
|
||
}
|
||
return "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject;
|
||
}
|
||
|
||
# return subject for existing tickets
|
||
if ( $Action eq 'Forward' ) {
|
||
if ($TicketSubjectFwd) {
|
||
$TicketSubjectFwd .= ': ';
|
||
}
|
||
if ( lc $TicketSubjectFormat eq 'right' ) {
|
||
return $TicketSubjectFwd . $Subject
|
||
. " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
|
||
}
|
||
if ( lc $TicketSubjectFormat eq 'none' ) {
|
||
return $TicketSubjectFwd . $Subject;
|
||
}
|
||
return
|
||
$TicketSubjectFwd
|
||
. "[$TicketHook$TicketHookDivider$Param{TicketNumber}] "
|
||
. $Subject;
|
||
}
|
||
else {
|
||
if ($TicketSubjectRe) {
|
||
$TicketSubjectRe .= ': ';
|
||
}
|
||
if ( lc $TicketSubjectFormat eq 'right' ) {
|
||
return $TicketSubjectRe . $Subject
|
||
. " [$TicketHook$TicketHookDivider$Param{TicketNumber}]";
|
||
}
|
||
if ( lc $TicketSubjectFormat eq 'none' ) {
|
||
return $TicketSubjectRe . $Subject;
|
||
}
|
||
return $TicketSubjectRe . "[$TicketHook$TicketHookDivider$Param{TicketNumber}] " . $Subject;
|
||
}
|
||
}
|
||
|
||
=head2 TicketSubjectClean()
|
||
|
||
strip/clean up a ticket subject
|
||
|
||
my $NewSubject = $TicketObject->TicketSubjectClean(
|
||
TicketNumber => '2004040510440485',
|
||
Subject => $OldSubject,
|
||
Size => $SubjectSizeToBeDisplayed # optional, if 0 do not cut subject
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketSubjectClean {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !defined $Param{TicketNumber} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketNumber!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
my $Subject = $Param{Subject} || '';
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# get config options
|
||
my $TicketHook = $ConfigObject->Get('Ticket::Hook');
|
||
my $TicketHookDivider = $ConfigObject->Get('Ticket::HookDivider');
|
||
my $TicketSubjectSize = $Param{Size};
|
||
if ( !defined $TicketSubjectSize ) {
|
||
$TicketSubjectSize = $ConfigObject->Get('Ticket::SubjectSize')
|
||
|| 120;
|
||
}
|
||
my $TicketSubjectRe = $ConfigObject->Get('Ticket::SubjectRe');
|
||
my $TicketSubjectFwd = $ConfigObject->Get('Ticket::SubjectFwd');
|
||
|
||
# remove all possible ticket hook formats with []
|
||
$Subject =~ s/\[\s*\Q$TicketHook: $Param{TicketNumber}\E\s*\]\s*//g;
|
||
$Subject =~ s/\[\s*\Q$TicketHook:$Param{TicketNumber}\E\s*\]\s*//g;
|
||
$Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*\]\s*//g;
|
||
|
||
# remove all ticket numbers with []
|
||
if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) {
|
||
$Subject =~ s/\[\s*\Q$TicketHook$TicketHookDivider\E\d+?\s*\]\s*//g;
|
||
}
|
||
|
||
# remove all possible ticket hook formats without []
|
||
$Subject =~ s/\Q$TicketHook: $Param{TicketNumber}\E\s*//g;
|
||
$Subject =~ s/\Q$TicketHook:$Param{TicketNumber}\E\s*//g;
|
||
$Subject =~ s/\Q$TicketHook$TicketHookDivider$Param{TicketNumber}\E\s*//g;
|
||
|
||
# remove all ticket numbers without []
|
||
if ( $ConfigObject->Get('Ticket::SubjectCleanAllNumbers') ) {
|
||
$Subject =~ s/\Q$TicketHook$TicketHookDivider\E\d+?\s*//g;
|
||
}
|
||
|
||
# remove leading number with configured "RE:\s" or "RE[\d+]:\s" e. g. "RE: " or "RE[4]: "
|
||
$Subject =~ s/^($TicketSubjectRe(\[\d+\])?:\s)+//i;
|
||
|
||
# remove leading number with configured "Fwd:\s" or "Fwd[\d+]:\s" e. g. "Fwd: " or "Fwd[4]: "
|
||
$Subject =~ s/^($TicketSubjectFwd(\[\d+\])?:\s)+//i;
|
||
|
||
# trim white space at the beginning or end
|
||
$Subject =~ s/(^\s+|\s+$)//;
|
||
|
||
# resize subject based on config
|
||
# do not cut subject, if size parameter was 0
|
||
if ($TicketSubjectSize) {
|
||
$Subject =~ s/^(.{$TicketSubjectSize}).*$/$1 [...]/;
|
||
}
|
||
|
||
return $Subject;
|
||
}
|
||
|
||
=head2 TicketGet()
|
||
|
||
Get ticket info
|
||
|
||
my %Ticket = $TicketObject->TicketGet(
|
||
TicketID => 123,
|
||
DynamicFields => 0, # Optional, default 0. To include the dynamic field values for this ticket on the return structure.
|
||
UserID => 123,
|
||
Silent => 0, # Optional, default 0. To suppress the warning if the ticket does not exist.
|
||
);
|
||
|
||
Returns:
|
||
|
||
%Ticket = (
|
||
TicketNumber => '20101027000001',
|
||
Title => 'some title',
|
||
TicketID => 123,
|
||
State => 'some state',
|
||
StateID => 123,
|
||
StateType => 'some state type',
|
||
Priority => 'some priority',
|
||
PriorityID => 123,
|
||
Lock => 'lock',
|
||
LockID => 123,
|
||
Queue => 'some queue',
|
||
QueueID => 123,
|
||
CustomerID => 'customer_id_123',
|
||
CustomerUserID => 'customer_user_id_123',
|
||
Owner => 'some_owner_login',
|
||
OwnerID => 123,
|
||
Type => 'some ticket type',
|
||
TypeID => 123,
|
||
SLA => 'some sla',
|
||
SLAID => 123,
|
||
Service => 'some service',
|
||
ServiceID => 123,
|
||
Responsible => 'some_responsible_login',
|
||
ResponsibleID => 123,
|
||
Age => 3456,
|
||
Created => '2010-10-27 20:15:00'
|
||
CreateBy => 123,
|
||
Changed => '2010-10-27 20:15:15',
|
||
ChangeBy => 123,
|
||
ArchiveFlag => 'y',
|
||
|
||
# If DynamicFields => 1 was passed, you'll get an entry like this for each dynamic field:
|
||
DynamicField_X => 'value_x',
|
||
|
||
# (time stamps of expected escalations)
|
||
EscalationResponseTime (unix time stamp of response time escalation)
|
||
EscalationUpdateTime (unix time stamp of update time escalation)
|
||
EscalationSolutionTime (unix time stamp of solution time escalation)
|
||
|
||
# (general escalation info of nearest escalation type)
|
||
EscalationDestinationIn (escalation in e. g. 1h 4m)
|
||
EscalationDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
EscalationDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
EscalationTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
EscalationTime (seconds total till escalation of nearest escalation time type - response, update or solution time, e. g. "3600")
|
||
|
||
# (detailed escalation info about first response, update and solution time)
|
||
FirstResponseTimeEscalation (if true, ticket is escalated)
|
||
FirstResponseTimeNotification (if true, notify - x% of escalation has reached)
|
||
FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
FirstResponseTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
FirstResponseTime (seconds total till escalation, e. g. "3600")
|
||
|
||
UpdateTimeEscalation (if true, ticket is escalated)
|
||
UpdateTimeNotification (if true, notify - x% of escalation has reached)
|
||
UpdateTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
UpdateTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
UpdateTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
UpdateTime (seconds total till escalation, e. g. "3600")
|
||
|
||
SolutionTimeEscalation (if true, ticket is escalated)
|
||
SolutionTimeNotification (if true, notify - x% of escalation has reached)
|
||
SolutionTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
SolutionTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
SolutionTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
SolutionTime (seconds total till escalation, e. g. "3600")
|
||
);
|
||
|
||
To get extended ticket attributes, use C<Extended>
|
||
|
||
my %Ticket = $TicketObject->TicketGet(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
Extended => 1,
|
||
);
|
||
|
||
Additional parameters are:
|
||
|
||
%Ticket = (
|
||
FirstResponse (timestamp of first response, first contact with customer)
|
||
FirstResponseInMin (minutes till first response)
|
||
FirstResponseDiffInMin (minutes till or over first response)
|
||
|
||
SolutionInMin (minutes till solution time)
|
||
SolutionDiffInMin (minutes till or over solution time)
|
||
|
||
FirstLock (timestamp of first lock)
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
$Param{Extended} = $Param{Extended} ? 1 : 0;
|
||
|
||
# Caching TicketGet() is a bit more complex than usual.
|
||
# The full function result will be cached in an in-memory cache to
|
||
# speed up subsequent operations in one request, but not on disk,
|
||
# because there are dependencies to other objects such as queue which cannot
|
||
# easily be tracked.
|
||
# The SQL for fetching ticket data will be cached on disk as well because this cache
|
||
# can easily be invalidated on ticket changes.
|
||
|
||
# check cache
|
||
my $FetchDynamicFields = $Param{DynamicFields} ? 1 : 0;
|
||
|
||
my $CacheKey = 'Cache::GetTicket' . $Param{TicketID};
|
||
my $CacheKeyDynamicFields
|
||
= 'Cache::GetTicket' . $Param{TicketID} . '::' . $Param{Extended} . '::' . $FetchDynamicFields;
|
||
|
||
my $CachedDynamicFields = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKeyDynamicFields,
|
||
CacheInMemory => 1,
|
||
CacheInBackend => 0,
|
||
);
|
||
|
||
# check if result is cached
|
||
if ( ref $CachedDynamicFields eq 'HASH' ) {
|
||
return %{$CachedDynamicFields};
|
||
}
|
||
|
||
my %Ticket;
|
||
|
||
my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKey,
|
||
);
|
||
|
||
if ( ref $Cached eq 'HASH' ) {
|
||
%Ticket = %{$Cached};
|
||
}
|
||
else {
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT st.id, st.queue_id, st.ticket_state_id, st.ticket_lock_id, st.ticket_priority_id,
|
||
st.create_time, st.create_time, st.tn, st.customer_id, st.customer_user_id,
|
||
st.user_id, st.responsible_user_id, st.until_time, st.change_time, st.title,
|
||
st.escalation_update_time, st.timeout, st.type_id, st.service_id, st.sla_id,
|
||
st.escalation_response_time, st.escalation_solution_time, st.escalation_time, st.archive_flag,
|
||
st.create_by, st.change_by
|
||
FROM ticket st
|
||
WHERE st.id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
Limit => 1,
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Ticket{TicketID} = $Row[0];
|
||
$Ticket{QueueID} = $Row[1];
|
||
$Ticket{StateID} = $Row[2];
|
||
$Ticket{LockID} = $Row[3];
|
||
$Ticket{PriorityID} = $Row[4];
|
||
|
||
$Ticket{Created} = $Row[5];
|
||
$Ticket{TicketNumber} = $Row[7];
|
||
$Ticket{CustomerID} = $Row[8];
|
||
$Ticket{CustomerUserID} = $Row[9];
|
||
|
||
$Ticket{OwnerID} = $Row[10];
|
||
$Ticket{ResponsibleID} = $Row[11] || 1;
|
||
$Ticket{RealTillTimeNotUsed} = $Row[12];
|
||
$Ticket{Changed} = $Row[13];
|
||
$Ticket{Title} = $Row[14];
|
||
|
||
$Ticket{EscalationUpdateTime} = $Row[15];
|
||
$Ticket{UnlockTimeout} = $Row[16];
|
||
$Ticket{TypeID} = $Row[17] || 1;
|
||
$Ticket{ServiceID} = $Row[18] || '';
|
||
$Ticket{SLAID} = $Row[19] || '';
|
||
|
||
$Ticket{EscalationResponseTime} = $Row[20];
|
||
$Ticket{EscalationSolutionTime} = $Row[21];
|
||
$Ticket{EscalationTime} = $Row[22];
|
||
$Ticket{ArchiveFlag} = $Row[23] ? 'y' : 'n';
|
||
|
||
$Ticket{CreateBy} = $Row[24];
|
||
$Ticket{ChangeBy} = $Row[25];
|
||
}
|
||
|
||
# use cache only when a ticket number is found otherwise a non-existant ticket
|
||
# is cached. That can cause errors when the cache isn't expired and postmaster
|
||
# creates that ticket
|
||
if ( $Ticket{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
||
Type => $Self->{CacheType},
|
||
TTL => $Self->{CacheTTL},
|
||
Key => $CacheKey,
|
||
|
||
# make a local copy of the ticket data to avoid it being altered in-memory later
|
||
Value => {%Ticket},
|
||
);
|
||
}
|
||
}
|
||
|
||
# check ticket
|
||
if ( !$Ticket{TicketID} ) {
|
||
if ( !$Param{Silent} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No such TicketID ($Param{TicketID})!",
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
# check if need to return DynamicFields
|
||
if ($FetchDynamicFields) {
|
||
|
||
# get dynamic field objects
|
||
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
|
||
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
||
|
||
# get all dynamic fields for the object type Ticket
|
||
my $DynamicFieldList = $DynamicFieldObject->DynamicFieldListGet(
|
||
ObjectType => 'Ticket'
|
||
);
|
||
|
||
DYNAMICFIELD:
|
||
for my $DynamicFieldConfig ( @{$DynamicFieldList} ) {
|
||
|
||
# validate each dynamic field
|
||
next DYNAMICFIELD if !$DynamicFieldConfig;
|
||
next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig);
|
||
next DYNAMICFIELD if !$DynamicFieldConfig->{Name};
|
||
|
||
# get the current value for each dynamic field
|
||
my $Value = $DynamicFieldBackendObject->ValueGet(
|
||
DynamicFieldConfig => $DynamicFieldConfig,
|
||
ObjectID => $Ticket{TicketID},
|
||
);
|
||
|
||
# set the dynamic field name and value into the ticket hash
|
||
$Ticket{ 'DynamicField_' . $DynamicFieldConfig->{Name} } = $Value;
|
||
}
|
||
}
|
||
|
||
my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
|
||
ID => $Ticket{QueueID},
|
||
);
|
||
|
||
$Ticket{Queue} = $Queue{Name};
|
||
$Ticket{GroupID} = $Queue{GroupID};
|
||
|
||
# fillup runtime values
|
||
my $TicketCreatedDTObj = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Ticket{Created}
|
||
},
|
||
);
|
||
|
||
my $Delta = $TicketCreatedDTObj->Delta( DateTimeObject => $Kernel::OM->Create('Kernel::System::DateTime') );
|
||
$Ticket{Age} = $Delta->{AbsoluteSeconds};
|
||
|
||
$Ticket{Priority} = $Kernel::OM->Get('Kernel::System::Priority')->PriorityLookup(
|
||
PriorityID => $Ticket{PriorityID},
|
||
);
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# get owner
|
||
$Ticket{Owner} = $UserObject->UserLookup(
|
||
UserID => $Ticket{OwnerID},
|
||
);
|
||
|
||
# get responsible
|
||
$Ticket{Responsible} = $UserObject->UserLookup(
|
||
UserID => $Ticket{ResponsibleID},
|
||
);
|
||
|
||
# get lock
|
||
$Ticket{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
|
||
LockID => $Ticket{LockID},
|
||
);
|
||
|
||
# get type
|
||
$Ticket{Type} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( TypeID => $Ticket{TypeID} );
|
||
|
||
# get service
|
||
if ( $Ticket{ServiceID} ) {
|
||
|
||
$Ticket{Service} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup(
|
||
ServiceID => $Ticket{ServiceID},
|
||
);
|
||
}
|
||
|
||
# get sla
|
||
if ( $Ticket{SLAID} ) {
|
||
$Ticket{SLA} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup(
|
||
SLAID => $Ticket{SLAID},
|
||
);
|
||
}
|
||
|
||
# get state info
|
||
my %StateData = $Kernel::OM->Get('Kernel::System::State')->StateGet(
|
||
ID => $Ticket{StateID}
|
||
);
|
||
|
||
$Ticket{StateType} = $StateData{TypeName};
|
||
$Ticket{State} = $StateData{Name};
|
||
|
||
if ( !$Ticket{RealTillTimeNotUsed} || lc $StateData{TypeName} eq 'pending' ) {
|
||
$Ticket{UntilTime} = 0;
|
||
}
|
||
else {
|
||
$Ticket{UntilTime} = $Ticket{RealTillTimeNotUsed} - $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch();
|
||
}
|
||
|
||
# get escalation attributes
|
||
my %Escalation = $Self->TicketEscalationDateCalculation(
|
||
Ticket => \%Ticket,
|
||
UserID => $Param{UserID} || 1,
|
||
);
|
||
|
||
for my $Key ( sort keys %Escalation ) {
|
||
$Ticket{$Key} = $Escalation{$Key};
|
||
}
|
||
|
||
# do extended lookups
|
||
if ( $Param{Extended} ) {
|
||
my %TicketExtended = $Self->_TicketGetExtended(
|
||
TicketID => $Param{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
for my $Key ( sort keys %TicketExtended ) {
|
||
$Ticket{$Key} = $TicketExtended{$Key};
|
||
}
|
||
}
|
||
|
||
# cache user result
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
||
Type => $Self->{CacheType},
|
||
TTL => $Self->{CacheTTL},
|
||
Key => $CacheKeyDynamicFields,
|
||
|
||
# make a local copy of the ticket data to avoid it being altered in-memory later
|
||
Value => {%Ticket},
|
||
CacheInMemory => 1,
|
||
CacheInBackend => 0,
|
||
);
|
||
|
||
return %Ticket;
|
||
}
|
||
|
||
=head2 TicketTitleUpdate()
|
||
|
||
update ticket title
|
||
|
||
my $Success = $TicketObject->TicketTitleUpdate(
|
||
Title => 'Some Title',
|
||
TicketID => 123,
|
||
UserID => 1,
|
||
);
|
||
|
||
Events:
|
||
TicketTitleUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketTitleUpdate {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(Title TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# check if update is needed
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
return 1 if defined $Ticket{Title} && $Ticket{Title} eq $Param{Title};
|
||
|
||
# db access
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET title = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{Title}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# truncate title
|
||
my $Title = substr( $Param{Title}, 0, 50 );
|
||
$Title .= '...' if length($Title) == 50;
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'TitleUpdate',
|
||
Name => "\%\%$Ticket{Title}\%\%$Title",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketTitleUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketUnlockTimeoutUpdate()
|
||
|
||
set the ticket unlock time to the passed time
|
||
|
||
my $Success = $TicketObject->TicketUnlockTimeoutUpdate(
|
||
UnlockTimeout => $Epoch,
|
||
TicketID => 123,
|
||
UserID => 143,
|
||
);
|
||
|
||
Events:
|
||
TicketUnlockTimeoutUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketUnlockTimeoutUpdate {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(UnlockTimeout TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# check if update is needed
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
return 1 if $Ticket{UnlockTimeout} eq $Param{UnlockTimeout};
|
||
|
||
# reset unlock time
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET timeout = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UnlockTimeout}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'Misc',
|
||
Name => Translatable('Reset of unlock time.'),
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketUnlockTimeoutUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketQueueID()
|
||
|
||
get ticket queue id
|
||
|
||
my $QueueID = $TicketObject->TicketQueueID(
|
||
TicketID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketQueueID {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get ticket data
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
DynamicFields => 0,
|
||
UserID => 1,
|
||
Silent => 1,
|
||
);
|
||
|
||
return if !%Ticket;
|
||
|
||
return $Ticket{QueueID};
|
||
}
|
||
|
||
=head2 TicketMoveList()
|
||
|
||
to get the move queue list for a ticket (depends on workflow, if configured)
|
||
|
||
my %Queues = $TicketObject->TicketMoveList(
|
||
Type => 'create',
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Queues = $TicketObject->TicketMoveList(
|
||
Type => 'create',
|
||
CustomerUserID => 'customer_user_id_123',
|
||
);
|
||
|
||
|
||
my %Queues = $TicketObject->TicketMoveList(
|
||
QueueID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Queues = $TicketObject->TicketMoveList(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketMoveList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need UserID or CustomerUserID!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} && !$Param{TicketID} && !$Param{Type} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need QueueID, TicketID or Type!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get queue object
|
||
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
|
||
|
||
my %Queues;
|
||
if ( $Param{UserID} && $Param{UserID} eq 1 ) {
|
||
%Queues = $QueueObject->GetAllQueues();
|
||
}
|
||
else {
|
||
%Queues = $QueueObject->GetAllQueues(%Param);
|
||
}
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'Queue',
|
||
Data => \%Queues,
|
||
);
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %Queues;
|
||
}
|
||
|
||
=head2 TicketQueueSet()
|
||
|
||
to move a ticket (sends notification to agents of selected my queues, if ticket is not closed)
|
||
|
||
my $Success = $TicketObject->TicketQueueSet(
|
||
QueueID => 123,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketQueueSet(
|
||
Queue => 'Some Queue Name',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketQueueSet(
|
||
Queue => 'Some Queue Name',
|
||
TicketID => 123,
|
||
Comment => 'some comment', # optional
|
||
ForceNotificationToUserID => [1,43,56], # if you want to force somebody
|
||
UserID => 123,
|
||
);
|
||
|
||
Optional attribute:
|
||
SendNoNotification disables or enables agent and customer notification for this
|
||
action.
|
||
|
||
For example:
|
||
|
||
SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
|
||
|
||
Events:
|
||
TicketQueueUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketQueueSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# get queue object
|
||
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
|
||
|
||
# queue lookup
|
||
if ( $Param{Queue} && !$Param{QueueID} ) {
|
||
$Param{QueueID} = $QueueObject->QueueLookup( Queue => $Param{Queue} );
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID QueueID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get current ticket
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# move needed?
|
||
if ( $Param{QueueID} == $Ticket{QueueID} && !$Param{Comment} ) {
|
||
|
||
# update not needed
|
||
return 1;
|
||
}
|
||
|
||
# permission check
|
||
my %MoveList = $Self->MoveList( %Param, Type => 'move_into' );
|
||
if ( !$MoveList{ $Param{QueueID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET queue_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{QueueID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# queue lookup
|
||
my $Queue = $QueueObject->QueueLookup( QueueID => $Param{QueueID} );
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
QueueID => $Param{QueueID},
|
||
HistoryType => 'Move',
|
||
Name => "\%\%$Queue\%\%$Param{QueueID}\%\%$Ticket{Queue}\%\%$Ticket{QueueID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# send move notify to queue subscriber
|
||
if ( !$Param{SendNoNotification} && $Ticket{StateType} ne 'closed' ) {
|
||
|
||
my @UserIDs;
|
||
|
||
if ( $Param{ForceNotificationToUserID} ) {
|
||
push @UserIDs, @{ $Param{ForceNotificationToUserID} };
|
||
}
|
||
|
||
# trigger notification event
|
||
$Self->EventHandler(
|
||
Event => 'NotificationMove',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
CustomerMessageParams => {
|
||
Queue => $Queue,
|
||
},
|
||
Recipients => \@UserIDs,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# trigger event, OldTicketData is needed for escalation events
|
||
$Self->EventHandler(
|
||
Event => 'TicketQueueUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
OldTicketData => \%Ticket,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketMoveQueueList()
|
||
|
||
returns a list of used queue ids / names
|
||
|
||
my @QueueIDList = $TicketObject->TicketMoveQueueList(
|
||
TicketID => 123,
|
||
Type => 'ID',
|
||
);
|
||
|
||
Returns:
|
||
|
||
@QueueIDList = ( 1, 2, 3 );
|
||
|
||
my @QueueList = $TicketObject->TicketMoveQueueList(
|
||
TicketID => 123,
|
||
Type => 'Name',
|
||
);
|
||
|
||
Returns:
|
||
|
||
@QueueList = ( 'QueueA', 'QueueB', 'QueueC' );
|
||
|
||
=cut
|
||
|
||
sub TicketMoveQueueList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketID!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT sh.name, ht.name, sh.create_by, sh.queue_id FROM '
|
||
. 'ticket_history sh, ticket_history_type ht WHERE '
|
||
. 'sh.ticket_id = ? AND ht.name IN (\'Move\', \'NewTicket\') AND '
|
||
. 'ht.id = sh.history_type_id ORDER BY sh.id',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
my @QueueID;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
|
||
# store result
|
||
if ( $Row[1] eq 'NewTicket' ) {
|
||
if ( $Row[3] ) {
|
||
push @QueueID, $Row[3];
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'Move' ) {
|
||
if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) {
|
||
push @QueueID, $2;
|
||
}
|
||
elsif ( $Row[0] =~ /^Ticket moved to Queue '.+?' \(ID=(.+?)\)/ ) {
|
||
push @QueueID, $1;
|
||
}
|
||
}
|
||
}
|
||
|
||
# get queue object
|
||
my $QueueObject = $Kernel::OM->Get('Kernel::System::Queue');
|
||
|
||
# queue lookup
|
||
my @QueueName;
|
||
for my $QueueID (@QueueID) {
|
||
|
||
my $Queue = $QueueObject->QueueLookup( QueueID => $QueueID );
|
||
|
||
push @QueueName, $Queue;
|
||
}
|
||
|
||
if ( $Param{Type} && $Param{Type} eq 'Name' ) {
|
||
return @QueueName;
|
||
}
|
||
else {
|
||
return @QueueID;
|
||
}
|
||
}
|
||
|
||
=head2 TicketTypeList()
|
||
|
||
to get all possible types for a ticket (depends on workflow, if configured)
|
||
|
||
my %Types = $TicketObject->TicketTypeList(
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Types = $TicketObject->TicketTypeList(
|
||
CustomerUserID => 'customer_user_id_123',
|
||
);
|
||
|
||
my %Types = $TicketObject->TicketTypeList(
|
||
QueueID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Types = $TicketObject->TicketTypeList(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
%Types = (
|
||
1 => 'default',
|
||
2 => 'request',
|
||
3 => 'offer',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketTypeList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need UserID or CustomerUserID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
my %Types = $Kernel::OM->Get('Kernel::System::Type')->TypeList( Valid => 1 );
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'Type',
|
||
Data => \%Types,
|
||
);
|
||
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %Types;
|
||
}
|
||
|
||
=head2 TicketTypeSet()
|
||
|
||
to set a ticket type
|
||
|
||
my $Success = $TicketObject->TicketTypeSet(
|
||
TypeID => 123,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketTypeSet(
|
||
Type => 'normal',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketTypeUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketTypeSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# type lookup
|
||
if ( $Param{Type} && !$Param{TypeID} ) {
|
||
$Param{TypeID} = $Kernel::OM->Get('Kernel::System::Type')->TypeLookup( Type => $Param{Type} );
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID TypeID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get current ticket
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# update needed?
|
||
return 1 if $Param{TypeID} == $Ticket{TypeID};
|
||
|
||
# permission check
|
||
my %TypeList = $Self->TicketTypeList(%Param);
|
||
if ( !$TypeList{ $Param{TypeID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET type_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{TypeID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# get new ticket data
|
||
my %TicketNew = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
$TicketNew{Type} = $TicketNew{Type} || 'NULL';
|
||
$Param{TypeID} = $Param{TypeID} || '';
|
||
$Ticket{Type} = $Ticket{Type} || 'NULL';
|
||
$Ticket{TypeID} = $Ticket{TypeID} || '';
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'TypeUpdate',
|
||
Name => "\%\%$TicketNew{Type}\%\%$Param{TypeID}\%\%$Ticket{Type}\%\%$Ticket{TypeID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketTypeUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketServiceList()
|
||
|
||
to get all possible services for a ticket (depends on workflow, if configured)
|
||
|
||
my %Services = $TicketObject->TicketServiceList(
|
||
QueueID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Services = $TicketObject->TicketServiceList(
|
||
CustomerUserID => 123,
|
||
QueueID => 123,
|
||
);
|
||
|
||
my %Services = $TicketObject->TicketServiceList(
|
||
CustomerUserID => 123,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
%Services = (
|
||
1 => 'ServiceA',
|
||
2 => 'ServiceB',
|
||
3 => 'ServiceC',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketServiceList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'UserID or CustomerUserID is needed!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} && !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'QueueID or TicketID is needed!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
my $ServiceObject = $Kernel::OM->Get('Kernel::System::Service');
|
||
|
||
# Return all Services, filtering by KeepChildren config.
|
||
my %AllServices = $ServiceObject->ServiceList(
|
||
UserID => 1,
|
||
KeepChildren =>
|
||
$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service::KeepChildren'),
|
||
);
|
||
|
||
my %Services;
|
||
if ( $Param{CustomerUserID} ) {
|
||
|
||
# Return all Services in relation with CustomerUser.
|
||
my %CustomerServices = $ServiceObject->CustomerUserServiceMemberList(
|
||
Result => 'HASH',
|
||
CustomerUserLogin => $Param{CustomerUserID},
|
||
UserID => 1,
|
||
);
|
||
|
||
# Filter Services based on relation with CustomerUser and KeepChildren config.
|
||
%Services = map { $_ => $CustomerServices{$_} } grep { defined $CustomerServices{$_} } sort keys %AllServices;
|
||
}
|
||
else {
|
||
%Services = %AllServices;
|
||
}
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'Service',
|
||
Data => \%Services,
|
||
);
|
||
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %Services;
|
||
}
|
||
|
||
=head2 TicketServiceSet()
|
||
|
||
to set a ticket service
|
||
|
||
my $Success = $TicketObject->TicketServiceSet(
|
||
ServiceID => 123,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketServiceSet(
|
||
Service => 'Service A',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketServiceUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketServiceSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# service lookup
|
||
if ( $Param{Service} && !$Param{ServiceID} ) {
|
||
$Param{ServiceID} = $Kernel::OM->Get('Kernel::System::Service')->ServiceLookup(
|
||
Name => $Param{Service},
|
||
);
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID ServiceID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get current ticket
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# update needed?
|
||
return 1 if $Param{ServiceID} eq $Ticket{ServiceID};
|
||
|
||
# permission check
|
||
my %ServiceList = $Self->TicketServiceList(%Param);
|
||
if ( $Param{ServiceID} ne '' && !$ServiceList{ $Param{ServiceID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check database undef/NULL (set value to undef/NULL to prevent database errors)
|
||
for my $Parameter (qw(ServiceID SLAID)) {
|
||
if ( !$Param{$Parameter} ) {
|
||
$Param{$Parameter} = undef;
|
||
}
|
||
}
|
||
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET service_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{ServiceID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# get new ticket data
|
||
my %TicketNew = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
$TicketNew{Service} = $TicketNew{Service} || 'NULL';
|
||
$Param{ServiceID} = $Param{ServiceID} || '';
|
||
$Ticket{Service} = $Ticket{Service} || 'NULL';
|
||
$Ticket{ServiceID} = $Ticket{ServiceID} || '';
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'ServiceUpdate',
|
||
Name =>
|
||
"\%\%$TicketNew{Service}\%\%$Param{ServiceID}\%\%$Ticket{Service}\%\%$Ticket{ServiceID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger notification event
|
||
$Self->EventHandler(
|
||
Event => 'NotificationServiceUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
CustomerMessageParams => {},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketServiceUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketEscalationPreferences()
|
||
|
||
get escalation preferences of a ticket (e. g. from SLA or from Queue based settings)
|
||
|
||
my %Escalation = $TicketObject->TicketEscalationPreferences(
|
||
Ticket => $Param{Ticket},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketEscalationPreferences {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(Ticket UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get ticket attributes
|
||
my %Ticket = %{ $Param{Ticket} };
|
||
|
||
# get escalation properties
|
||
my %Escalation;
|
||
if ( $Kernel::OM->Get('Kernel::Config')->Get('Ticket::Service') && $Ticket{SLAID} ) {
|
||
|
||
%Escalation = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet(
|
||
SLAID => $Ticket{SLAID},
|
||
UserID => $Param{UserID},
|
||
Cache => 1,
|
||
);
|
||
}
|
||
else {
|
||
%Escalation = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
|
||
ID => $Ticket{QueueID},
|
||
UserID => $Param{UserID},
|
||
Cache => 1,
|
||
);
|
||
}
|
||
|
||
return %Escalation;
|
||
}
|
||
|
||
=head2 TicketEscalationDateCalculation()
|
||
|
||
get escalation properties of a ticket
|
||
|
||
my %Escalation = $TicketObject->TicketEscalationDateCalculation(
|
||
Ticket => $Param{Ticket},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
returns
|
||
|
||
(general escalation info)
|
||
EscalationDestinationIn (escalation in e. g. 1h 4m)
|
||
EscalationDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
EscalationDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
EscalationTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
EscalationTime (seconds total till escalation, e. g. "3600")
|
||
|
||
(detail escalation info about first response, update and solution time)
|
||
FirstResponseTimeEscalation (if true, ticket is escalated)
|
||
FirstResponseTimeNotification (if true, notify - x% of escalation has reached)
|
||
FirstResponseTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
FirstResponseTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
FirstResponseTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
FirstResponseTime (seconds total till escalation, e. g. "3600")
|
||
|
||
UpdateTimeEscalation (if true, ticket is escalated)
|
||
UpdateTimeNotification (if true, notify - x% of escalation has reached)
|
||
UpdateTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
UpdateTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
UpdateTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
UpdateTime (seconds total till escalation, e. g. "3600")
|
||
|
||
SolutionTimeEscalation (if true, ticket is escalated)
|
||
SolutionTimeNotification (if true, notify - x% of escalation has reached)
|
||
SolutionTimeDestinationTime (date of escalation in unix time, e. g. 72193292)
|
||
SolutionTimeDestinationDate (date of escalation, e. g. "2009-02-14 18:00:00")
|
||
SolutionTimeWorkingTime (seconds of working/service time till escalation, e. g. "1800")
|
||
SolutionTime (seconds total till escalation, e. g. "3600")
|
||
|
||
=cut
|
||
|
||
sub TicketEscalationDateCalculation {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(Ticket UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get ticket attributes
|
||
my %Ticket = %{ $Param{Ticket} };
|
||
|
||
# do no escalations on (merge|close|remove) tickets
|
||
return if $Ticket{StateType} eq 'merged';
|
||
return if $Ticket{StateType} eq 'closed';
|
||
return if $Ticket{StateType} eq 'removed';
|
||
|
||
# get escalation properties
|
||
my %Escalation = $Self->TicketEscalationPreferences(
|
||
Ticket => $Param{Ticket},
|
||
UserID => $Param{UserID} || 1,
|
||
);
|
||
|
||
# return if we do not have any escalation attributes
|
||
my %Map = (
|
||
EscalationResponseTime => 'FirstResponse',
|
||
EscalationUpdateTime => 'Update',
|
||
EscalationSolutionTime => 'Solution',
|
||
);
|
||
my $EscalationAttribute;
|
||
KEY:
|
||
for my $Key ( sort keys %Map ) {
|
||
if ( $Escalation{ $Map{$Key} . 'Time' } ) {
|
||
$EscalationAttribute = 1;
|
||
last KEY;
|
||
}
|
||
}
|
||
|
||
return if !$EscalationAttribute;
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||
|
||
# calculate escalation times based on escalation properties
|
||
my %Data;
|
||
|
||
TIME:
|
||
for my $Key ( sort keys %Map ) {
|
||
|
||
next TIME if !$Ticket{$Key};
|
||
|
||
# get time before or over escalation (escalation_destination_unixtime - now)
|
||
my $TimeTillEscalation = $Ticket{$Key} - $DateTimeObject->ToEpoch();
|
||
|
||
# ticket is not escalated till now ($TimeTillEscalation > 0)
|
||
my $WorkingTime = 0;
|
||
if ( $TimeTillEscalation > 0 ) {
|
||
|
||
my $StopTimeObj = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
Epoch => $Ticket{$Key}
|
||
}
|
||
);
|
||
|
||
my $DeltaObj = $DateTimeObject->Delta(
|
||
DateTimeObject => $StopTimeObj,
|
||
ForWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
$WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
|
||
|
||
# extract needed data
|
||
my $Notify = $Escalation{ $Map{$Key} . 'Notify' };
|
||
my $Time = $Escalation{ $Map{$Key} . 'Time' };
|
||
|
||
# set notification if notify % is reached
|
||
if ( $Notify && $Time ) {
|
||
|
||
my $Reached = 100 - ( $WorkingTime / ( $Time * 60 / 100 ) );
|
||
|
||
if ( $Reached >= $Notify ) {
|
||
$Data{ $Map{$Key} . 'TimeNotification' } = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
# ticket is overtime ($TimeTillEscalation < 0)
|
||
else {
|
||
my $StartTimeObj = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
Epoch => $Ticket{$Key}
|
||
}
|
||
);
|
||
|
||
my $DeltaObj = $StartTimeObj->Delta(
|
||
DateTimeObject => $DateTimeObject,
|
||
ForWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
$WorkingTime = 0;
|
||
if ( $DeltaObj && $DeltaObj->{AbsoluteSeconds} ) {
|
||
$WorkingTime = '-' . $DeltaObj->{AbsoluteSeconds};
|
||
}
|
||
|
||
# set escalation
|
||
$Data{ $Map{$Key} . 'TimeEscalation' } = 1;
|
||
}
|
||
|
||
my $DestinationDate = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
Epoch => $Ticket{$Key}
|
||
}
|
||
);
|
||
|
||
$Data{ $Map{$Key} . 'TimeDestinationTime' } = $Ticket{$Key};
|
||
$Data{ $Map{$Key} . 'TimeDestinationDate' } = $DestinationDate->ToString();
|
||
$Data{ $Map{$Key} . 'TimeWorkingTime' } = $WorkingTime;
|
||
$Data{ $Map{$Key} . 'Time' } = $TimeTillEscalation;
|
||
|
||
# set global escalation attributes (set the escalation which is the first in time)
|
||
if (
|
||
!$Data{EscalationDestinationTime}
|
||
|| $Data{EscalationDestinationTime} > $Ticket{$Key}
|
||
)
|
||
{
|
||
$Data{EscalationDestinationTime} = $Ticket{$Key};
|
||
$Data{EscalationDestinationDate} = $DestinationDate->ToString();
|
||
$Data{EscalationTimeWorkingTime} = $WorkingTime;
|
||
$Data{EscalationTime} = $TimeTillEscalation;
|
||
|
||
# escalation time in readable way
|
||
$Data{EscalationDestinationIn} = '';
|
||
$WorkingTime = abs($WorkingTime);
|
||
if ( $WorkingTime >= 3600 ) {
|
||
$Data{EscalationDestinationIn} .= int( $WorkingTime / 3600 ) . 'h ';
|
||
$WorkingTime = $WorkingTime
|
||
- ( int( $WorkingTime / 3600 ) * 3600 ); # remove already shown hours
|
||
}
|
||
if ( $WorkingTime <= 3600 || int( $WorkingTime / 60 ) ) {
|
||
$Data{EscalationDestinationIn} .= int( $WorkingTime / 60 ) . 'm';
|
||
}
|
||
}
|
||
}
|
||
|
||
return %Data;
|
||
}
|
||
|
||
=head2 TicketEscalationIndexBuild()
|
||
|
||
build escalation index of one ticket with current settings (SLA, Queue, Calendar...)
|
||
|
||
my $Success = $TicketObject->TicketEscalationIndexBuild(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketEscalationIndexBuild {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
DynamicFields => 0,
|
||
Silent => 1, # Suppress warning if the ticket was deleted in the meantime.
|
||
);
|
||
|
||
return if !%Ticket;
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# do no escalations on (merge|close|remove) tickets
|
||
if ( $Ticket{StateType} && $Ticket{StateType} =~ /^(merge|close|remove)/i ) {
|
||
|
||
# update escalation times with 0
|
||
my %EscalationTimes = (
|
||
EscalationTime => 'escalation_time',
|
||
EscalationResponseTime => 'escalation_response_time',
|
||
EscalationUpdateTime => 'escalation_update_time',
|
||
EscalationSolutionTime => 'escalation_solution_time',
|
||
);
|
||
|
||
TIME:
|
||
for my $Key ( sort keys %EscalationTimes ) {
|
||
|
||
# check if table update is needed
|
||
next TIME if !$Ticket{$Key};
|
||
|
||
# update ticket table
|
||
$DBObject->Do(
|
||
SQL =>
|
||
"UPDATE ticket SET $EscalationTimes{$Key} = 0, change_time = current_timestamp, "
|
||
. " change_by = ? WHERE id = ?",
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
|
||
);
|
||
}
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
return 1;
|
||
}
|
||
|
||
# get escalation properties
|
||
my %Escalation;
|
||
if (%Ticket) {
|
||
%Escalation = $Self->TicketEscalationPreferences(
|
||
Ticket => \%Ticket,
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# find escalation times
|
||
my $EscalationTime = 0;
|
||
|
||
# update first response (if not responded till now)
|
||
if ( !$Escalation{FirstResponseTime} ) {
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
}
|
||
else {
|
||
|
||
# check if first response is already done
|
||
my %FirstResponseDone = $Self->_TicketGetFirstResponse(
|
||
TicketID => $Ticket{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
# update first response time to 0
|
||
if (%FirstResponseDone) {
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_response_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
}
|
||
|
||
# update first response time to expected escalation destination time
|
||
else {
|
||
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Ticket{Created},
|
||
}
|
||
);
|
||
|
||
$DateTimeObject->Add(
|
||
AsWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
Seconds => $Escalation{FirstResponseTime} * 60,
|
||
);
|
||
|
||
my $DestinationTime = $DateTimeObject->ToEpoch();
|
||
|
||
# update first response time to $DestinationTime
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_response_time = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
|
||
# remember escalation time
|
||
$EscalationTime = $DestinationTime;
|
||
}
|
||
}
|
||
|
||
# update update && do not escalate in "pending auto" for escalation update time
|
||
if ( !$Escalation{UpdateTime} || $Ticket{StateType} =~ /^(pending)/i ) {
|
||
$DBObject->Do(
|
||
SQL => 'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
}
|
||
else {
|
||
|
||
# check if update escalation should be set
|
||
my @SenderHistory;
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT article_sender_type_id, is_visible_for_customer, create_time FROM '
|
||
. 'article WHERE ticket_id = ? ORDER BY create_time ASC',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
push @SenderHistory, {
|
||
SenderTypeID => $Row[0],
|
||
IsVisibleForCustomer => $Row[1],
|
||
Created => $Row[2],
|
||
};
|
||
}
|
||
|
||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||
|
||
# fill up lookups
|
||
for my $Row (@SenderHistory) {
|
||
$Row->{SenderType} = $ArticleObject->ArticleSenderTypeLookup(
|
||
SenderTypeID => $Row->{SenderTypeID},
|
||
);
|
||
}
|
||
|
||
# get latest customer contact time
|
||
my $LastSenderTime;
|
||
my $LastSenderType = '';
|
||
ROW:
|
||
for my $Row ( reverse @SenderHistory ) {
|
||
|
||
# fill up latest sender time (as initial value)
|
||
if ( !$LastSenderTime ) {
|
||
$LastSenderTime = $Row->{Created};
|
||
}
|
||
|
||
# do not use locked tickets for calculation
|
||
#last ROW if $Ticket{Lock} eq 'lock';
|
||
|
||
# do not use internal articles for calculation
|
||
next ROW if !$Row->{IsVisibleForCustomer};
|
||
|
||
# only use 'agent' and 'customer' sender types for calculation
|
||
next ROW if $Row->{SenderType} !~ /^(agent|customer)$/;
|
||
|
||
# last ROW if latest was customer and the next was not customer
|
||
# otherwise use also next, older customer article as latest
|
||
# customer followup for starting escalation
|
||
if ( $Row->{SenderType} eq 'agent' && $LastSenderType eq 'customer' ) {
|
||
last ROW;
|
||
}
|
||
|
||
# start escalation on latest customer article
|
||
if ( $Row->{SenderType} eq 'customer' ) {
|
||
$LastSenderType = 'customer';
|
||
$LastSenderTime = $Row->{Created};
|
||
}
|
||
|
||
# start escalation on latest agent article
|
||
if ( $Row->{SenderType} eq 'agent' ) {
|
||
$LastSenderTime = $Row->{Created};
|
||
last ROW;
|
||
}
|
||
}
|
||
if ($LastSenderTime) {
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $LastSenderTime,
|
||
}
|
||
);
|
||
|
||
$DateTimeObject->Add(
|
||
Seconds => $Escalation{UpdateTime} * 60,
|
||
AsWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
my $DestinationTime = $DateTimeObject->ToEpoch();
|
||
|
||
# update update time to $DestinationTime
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_update_time = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
|
||
# remember escalation time
|
||
if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) {
|
||
$EscalationTime = $DestinationTime;
|
||
}
|
||
}
|
||
|
||
# else, no not escalate, because latest sender was agent
|
||
else {
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_update_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ]
|
||
);
|
||
}
|
||
}
|
||
|
||
# update solution
|
||
if ( !$Escalation{SolutionTime} ) {
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
|
||
);
|
||
}
|
||
else {
|
||
|
||
# find solution time / first close time
|
||
my %SolutionDone = $Self->_TicketGetClosed(
|
||
TicketID => $Ticket{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
# update solution time to 0
|
||
if (%SolutionDone) {
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_solution_time = 0, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Ticket{TicketID}, ],
|
||
);
|
||
}
|
||
else {
|
||
|
||
# get datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Ticket{Created},
|
||
}
|
||
);
|
||
|
||
$DateTimeObject->Add(
|
||
Seconds => $Escalation{SolutionTime} * 60,
|
||
AsWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
my $DestinationTime = $DateTimeObject->ToEpoch();
|
||
|
||
# update solution time to $DestinationTime
|
||
$DBObject->Do(
|
||
SQL =>
|
||
'UPDATE ticket SET escalation_solution_time = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$DestinationTime, \$Param{UserID}, \$Ticket{TicketID}, ],
|
||
);
|
||
|
||
# remember escalation time
|
||
if ( $EscalationTime == 0 || $DestinationTime < $EscalationTime ) {
|
||
$EscalationTime = $DestinationTime;
|
||
}
|
||
}
|
||
}
|
||
|
||
# update escalation time (< escalation time)
|
||
if ( defined $EscalationTime ) {
|
||
$DBObject->Do(
|
||
SQL => 'UPDATE ticket SET escalation_time = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$EscalationTime, \$Param{UserID}, \$Ticket{TicketID}, ],
|
||
);
|
||
}
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketSLAList()
|
||
|
||
to get all possible SLAs for a ticket (depends on workflow, if configured)
|
||
|
||
my %SLAs = $TicketObject->TicketSLAList(
|
||
ServiceID => 1,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %SLAs = $TicketObject->TicketSLAList(
|
||
ServiceID => 1,
|
||
CustomerUserID => 'customer_user_id_123',
|
||
);
|
||
|
||
|
||
my %SLAs = $TicketObject->TicketSLAList(
|
||
QueueID => 123,
|
||
ServiceID => 1,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %SLAs = $TicketObject->TicketSLAList(
|
||
TicketID => 123,
|
||
ServiceID => 1,
|
||
UserID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
%SLAs = (
|
||
1 => 'SLA A',
|
||
2 => 'SLA B',
|
||
3 => 'SLA C',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketSLAList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need UserID or CustomerUserID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} && !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need QueueID or TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# return emty hash, if no service id is given
|
||
if ( !$Param{ServiceID} ) {
|
||
return ();
|
||
}
|
||
|
||
# get sla list
|
||
my %SLAs = $Kernel::OM->Get('Kernel::System::SLA')->SLAList(
|
||
ServiceID => $Param{ServiceID},
|
||
UserID => 1,
|
||
);
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'SLA',
|
||
Data => \%SLAs,
|
||
);
|
||
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %SLAs;
|
||
}
|
||
|
||
=head2 TicketSLASet()
|
||
|
||
to set a ticket service level agreement
|
||
|
||
my $Success = $TicketObject->TicketSLASet(
|
||
SLAID => 123,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketSLASet(
|
||
SLA => 'SLA A',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketSLAUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketSLASet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# sla lookup
|
||
if ( $Param{SLA} && !$Param{SLAID} ) {
|
||
$Param{SLAID} = $Kernel::OM->Get('Kernel::System::SLA')->SLALookup( Name => $Param{SLA} );
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID SLAID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get current ticket
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# update needed?
|
||
return 1 if ( $Param{SLAID} eq $Ticket{SLAID} );
|
||
|
||
# permission check
|
||
my %SLAList = $Self->TicketSLAList(
|
||
%Param,
|
||
ServiceID => $Ticket{ServiceID},
|
||
);
|
||
|
||
if ( $Param{UserID} != 1 && $Param{SLAID} ne '' && !$SLAList{ $Param{SLAID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check database undef/NULL (set value to undef/NULL to prevent database errors)
|
||
for my $Parameter (qw(ServiceID SLAID)) {
|
||
if ( !$Param{$Parameter} ) {
|
||
$Param{$Parameter} = undef;
|
||
}
|
||
}
|
||
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET sla_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{SLAID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# get new ticket data
|
||
my %TicketNew = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
$TicketNew{SLA} = $TicketNew{SLA} || 'NULL';
|
||
$Param{SLAID} = $Param{SLAID} || '';
|
||
$Ticket{SLA} = $Ticket{SLA} || 'NULL';
|
||
$Ticket{SLAID} = $Ticket{SLAID} || '';
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'SLAUpdate',
|
||
Name => "\%\%$TicketNew{SLA}\%\%$Param{SLAID}\%\%$Ticket{SLA}\%\%$Ticket{SLAID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event, OldTicketData is needed for escalation events
|
||
$Self->EventHandler(
|
||
Event => 'TicketSLAUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
OldTicketData => \%Ticket,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketCustomerSet()
|
||
|
||
Set customer data of ticket. Can set 'No' (CustomerID),
|
||
'User' (CustomerUserID), or both.
|
||
|
||
my $Success = $TicketObject->TicketCustomerSet(
|
||
No => 'client123',
|
||
User => 'client-user-123',
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
Events:
|
||
TicketCustomerUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketCustomerSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if ( !defined $Param{No} && !defined $Param{User} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need User or No for update!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db customer id update
|
||
if ( defined $Param{No} ) {
|
||
|
||
my $Ok = $DBObject->Do(
|
||
SQL => 'UPDATE ticket SET customer_id = ?, '
|
||
. ' change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{No}, \$Param{UserID}, \$Param{TicketID} ]
|
||
);
|
||
|
||
if ($Ok) {
|
||
$Param{History} = "CustomerID=$Param{No};";
|
||
}
|
||
}
|
||
|
||
# db customer user update
|
||
if ( defined $Param{User} ) {
|
||
|
||
my $Ok = $DBObject->Do(
|
||
SQL => 'UPDATE ticket SET customer_user_id = ?, '
|
||
. 'change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{User}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
if ($Ok) {
|
||
$Param{History} .= "CustomerUser=$Param{User};";
|
||
}
|
||
}
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# if no change
|
||
if ( !$Param{History} ) {
|
||
return;
|
||
}
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'CustomerUpdate',
|
||
Name => "\%\%" . $Param{History},
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketCustomerUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketPermission()
|
||
|
||
returns whether or not the agent has permission on a ticket
|
||
|
||
my $Access = $TicketObject->TicketPermission(
|
||
Type => 'ro',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
or without logging, for example for to check if a link/action should be shown
|
||
|
||
my $Access = $TicketObject->TicketPermission(
|
||
Type => 'ro',
|
||
TicketID => 123,
|
||
LogNo => 1,
|
||
UserID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketPermission {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(Type TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get needed objects
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
||
|
||
# run all TicketPermission modules
|
||
if ( ref $ConfigObject->Get('Ticket::Permission') eq 'HASH' ) {
|
||
my %Modules = %{ $ConfigObject->Get('Ticket::Permission') };
|
||
|
||
MODULE:
|
||
for my $Module ( sort keys %Modules ) {
|
||
|
||
# log try of load module
|
||
if ( $Self->{Debug} > 1 ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'debug',
|
||
Message => "Try to load module: $Modules{$Module}->{Module}!",
|
||
);
|
||
}
|
||
|
||
# load module
|
||
next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} );
|
||
|
||
# create object
|
||
my $ModuleObject = $Modules{$Module}->{Module}->new();
|
||
|
||
# execute Run()
|
||
my $AccessOk = $ModuleObject->Run(%Param);
|
||
|
||
# check granted option (should I say ok)
|
||
if ( $AccessOk && $Modules{$Module}->{Granted} ) {
|
||
if ( $Self->{Debug} > 0 ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'debug',
|
||
Message => "Granted access '$Param{Type}' true for "
|
||
. "TicketID '$Param{TicketID}' "
|
||
. "through $Modules{$Module}->{Module} (no more checks)!",
|
||
);
|
||
}
|
||
|
||
# access ok
|
||
return 1;
|
||
}
|
||
|
||
# return because access is false but it's required
|
||
if ( !$AccessOk && $Modules{$Module}->{Required} ) {
|
||
if ( !$Param{LogNo} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied because module "
|
||
. "($Modules{$Module}->{Module}) is required "
|
||
. "(UserID: $Param{UserID} '$Param{Type}' on "
|
||
. "TicketID: $Param{TicketID})!",
|
||
);
|
||
}
|
||
|
||
# access not ok
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
# don't grant access to the ticket
|
||
if ( !$Param{LogNo} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' "
|
||
. "on TicketID: $Param{TicketID})!",
|
||
);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
=head2 TicketCustomerPermission()
|
||
|
||
returns whether or not a customer has permission to a ticket
|
||
|
||
my $Access = $TicketObject->TicketCustomerPermission(
|
||
Type => 'ro',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
or without logging, for example for to check if a link/action should be displayed
|
||
|
||
my $Access = $TicketObject->TicketCustomerPermission(
|
||
Type => 'ro',
|
||
TicketID => 123,
|
||
LogNo => 1,
|
||
UserID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketCustomerPermission {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(Type TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get main object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
||
|
||
# run all CustomerTicketPermission modules
|
||
if ( ref $ConfigObject->Get('CustomerTicket::Permission') eq 'HASH' ) {
|
||
my %Modules = %{ $ConfigObject->Get('CustomerTicket::Permission') };
|
||
|
||
MODULE:
|
||
for my $Module ( sort keys %Modules ) {
|
||
|
||
# log try of load module
|
||
if ( $Self->{Debug} > 1 ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'debug',
|
||
Message => "Try to load module: $Modules{$Module}->{Module}!",
|
||
);
|
||
}
|
||
|
||
# load module
|
||
next MODULE if !$MainObject->Require( $Modules{$Module}->{Module} );
|
||
|
||
# create object
|
||
my $ModuleObject = $Modules{$Module}->{Module}->new();
|
||
|
||
# execute Run()
|
||
my $AccessOk = $ModuleObject->Run(%Param);
|
||
|
||
# check granted option (should I say ok)
|
||
if ( $AccessOk && $Modules{$Module}->{Granted} ) {
|
||
if ( $Self->{Debug} > 0 ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'debug',
|
||
Message => "Granted access '$Param{Type}' true for "
|
||
. "TicketID '$Param{TicketID}' "
|
||
. "through $Modules{$Module}->{Module} (no more checks)!",
|
||
);
|
||
}
|
||
|
||
# access ok
|
||
return 1;
|
||
}
|
||
|
||
# return because access is false but it's required
|
||
if ( !$AccessOk && $Modules{$Module}->{Required} ) {
|
||
if ( !$Param{LogNo} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied because module "
|
||
. "($Modules{$Module}->{Module}) is required "
|
||
. "(UserID: $Param{UserID} '$Param{Type}' on "
|
||
. "TicketID: $Param{TicketID})!",
|
||
);
|
||
}
|
||
|
||
# access not ok
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
# don't grant access to the ticket
|
||
if ( !$Param{LogNo} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied (UserID: $Param{UserID} '$Param{Type}' on "
|
||
. "TicketID: $Param{TicketID})!",
|
||
);
|
||
}
|
||
return;
|
||
}
|
||
|
||
=head2 GetSubscribedUserIDsByQueueID()
|
||
|
||
returns an array of user ids which selected the given queue id as
|
||
custom queue.
|
||
|
||
my @UserIDs = $TicketObject->GetSubscribedUserIDsByQueueID(
|
||
QueueID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
@UserIDs = ( 1, 2, 3 );
|
||
|
||
=cut
|
||
|
||
sub GetSubscribedUserIDsByQueueID {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need QueueID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get group of queue
|
||
my %Queue = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet( ID => $Param{QueueID} );
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# fetch all queues
|
||
my @UserIDs;
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT user_id FROM personal_queues WHERE queue_id = ?',
|
||
Bind => [ \$Param{QueueID} ],
|
||
);
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
push @UserIDs, $Row[0];
|
||
}
|
||
|
||
# get needed objects
|
||
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# check if user is valid and check permissions
|
||
my @CleanUserIDs;
|
||
|
||
USER:
|
||
for my $UserID (@UserIDs) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $UserID,
|
||
Valid => 1
|
||
);
|
||
|
||
next USER if !%User;
|
||
|
||
# just send emails to permitted agents
|
||
my %GroupMember = $GroupObject->PermissionUserGet(
|
||
UserID => $UserID,
|
||
Type => 'ro',
|
||
);
|
||
|
||
if ( $GroupMember{ $Queue{GroupID} } ) {
|
||
push @CleanUserIDs, $UserID;
|
||
}
|
||
}
|
||
|
||
return @CleanUserIDs;
|
||
}
|
||
|
||
=head2 GetSubscribedUserIDsByServiceID()
|
||
|
||
returns an array of user ids which selected the given service id as
|
||
custom service.
|
||
|
||
my @UserIDs = $TicketObject->GetSubscribedUserIDsByServiceID(
|
||
ServiceID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
@UserIDs = ( 1, 2, 3 );
|
||
|
||
=cut
|
||
|
||
sub GetSubscribedUserIDsByServiceID {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{ServiceID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need ServiceID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# fetch all users
|
||
my @UserIDs;
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT user_id
|
||
FROM personal_services
|
||
WHERE service_id = ?',
|
||
Bind => [ \$Param{ServiceID} ],
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
push @UserIDs, $Row[0];
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# check if user is valid
|
||
my @CleanUserIDs;
|
||
USER:
|
||
for my $UserID (@UserIDs) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $UserID,
|
||
Valid => 1,
|
||
);
|
||
|
||
next USER if !%User;
|
||
|
||
push @CleanUserIDs, $UserID;
|
||
}
|
||
|
||
return @CleanUserIDs;
|
||
}
|
||
|
||
=head2 TicketPendingTimeSet()
|
||
|
||
set ticket pending time:
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
Year => 2003,
|
||
Month => 08,
|
||
Day => 14,
|
||
Hour => 22,
|
||
Minute => 05,
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
or use a time stamp:
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
String => '2003-08-14 22:05:00',
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
or use a diff (set pending time to "now" + diff minutes)
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
Diff => ( 7 * 24 * 60 ), # minutes (here: 10080 minutes - 7 days)
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
If you want to set the pending time to null, just supply zeros:
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
Year => 0000,
|
||
Month => 00,
|
||
Day => 00,
|
||
Hour => 00,
|
||
Minute => 00,
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
or use a time stamp:
|
||
|
||
my $Success = $TicketObject->TicketPendingTimeSet(
|
||
String => '0000-00-00 00:00:00',
|
||
TicketID => 123,
|
||
UserID => 23,
|
||
);
|
||
|
||
Events:
|
||
TicketPendingTimeUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketPendingTimeSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{String} && !$Param{Diff} ) {
|
||
for my $Needed (qw(Year Month Day Hour Minute TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
elsif (
|
||
!$Param{String} &&
|
||
!( $Param{Year} && $Param{Month} && $Param{Day} && $Param{Hour} && $Param{Minute} )
|
||
)
|
||
{
|
||
for my $Needed (qw(Diff TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
for my $Needed (qw(String TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
# check if we need to null the PendingTime
|
||
my $PendingTimeNull;
|
||
if ( $Param{String} && $Param{String} eq '0000-00-00 00:00:00' ) {
|
||
$PendingTimeNull = 1;
|
||
$Param{Sec} = 0;
|
||
$Param{Minute} = 0;
|
||
$Param{Hour} = 0;
|
||
$Param{Day} = 0;
|
||
$Param{Month} = 0;
|
||
$Param{Year} = 0;
|
||
}
|
||
elsif (
|
||
!$Param{String}
|
||
&& !$Param{Diff}
|
||
&& $Param{Minute} == 0
|
||
&& $Param{Hour} == 0 && $Param{Day} == 0
|
||
&& $Param{Month} == 0
|
||
&& $Param{Year} == 0
|
||
)
|
||
{
|
||
$PendingTimeNull = 1;
|
||
}
|
||
|
||
# get system time from string/params
|
||
my $Time = 0;
|
||
if ( !$PendingTimeNull ) {
|
||
|
||
if ( $Param{String} ) {
|
||
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Param{String}
|
||
}
|
||
);
|
||
return if ( !$DateTimeObject );
|
||
|
||
$Time = $DateTimeObject->ToEpoch();
|
||
|
||
my $DateTimeValues = $DateTimeObject->Get();
|
||
$Param{Sec} = $DateTimeValues->{Second};
|
||
$Param{Minute} = $DateTimeValues->{Minute};
|
||
$Param{Hour} = $DateTimeValues->{Hour};
|
||
$Param{Day} = $DateTimeValues->{Day};
|
||
$Param{Month} = $DateTimeValues->{Month};
|
||
$Param{Year} = $DateTimeValues->{Year};
|
||
}
|
||
elsif ( $Param{Diff} ) {
|
||
|
||
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||
$DateTimeObject->Add( Minutes => $Param{Diff} );
|
||
|
||
$Time = $DateTimeObject->ToEpoch();
|
||
|
||
my $DateTimeValues = $DateTimeObject->Get();
|
||
$Param{Sec} = $DateTimeValues->{Second};
|
||
$Param{Minute} = $DateTimeValues->{Minute};
|
||
$Param{Hour} = $DateTimeValues->{Hour};
|
||
$Param{Day} = $DateTimeValues->{Day};
|
||
$Param{Month} = $DateTimeValues->{Month};
|
||
$Param{Year} = $DateTimeValues->{Year};
|
||
}
|
||
else {
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => "$Param{Year}-$Param{Month}-$Param{Day} $Param{Hour}:$Param{Minute}:00",
|
||
}
|
||
);
|
||
return if !$DateTimeObject;
|
||
$Time = $DateTimeObject->ToEpoch();
|
||
}
|
||
|
||
# return if no convert is possible
|
||
return if !$Time;
|
||
}
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET until_time = ?, change_time = current_timestamp, change_by = ?'
|
||
. ' WHERE id = ?',
|
||
Bind => [ \$Time, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# history insert
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
HistoryType => 'SetPendingTime',
|
||
Name => '%%'
|
||
. sprintf( "%02d", $Param{Year} ) . '-'
|
||
. sprintf( "%02d", $Param{Month} ) . '-'
|
||
. sprintf( "%02d", $Param{Day} ) . ' '
|
||
. sprintf( "%02d", $Param{Hour} ) . ':'
|
||
. sprintf( "%02d", $Param{Minute} ) . '',
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketPendingTimeUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketLockGet()
|
||
|
||
check if a ticket is locked or not
|
||
|
||
if ($TicketObject->TicketLockGet(TicketID => 123)) {
|
||
print "Ticket is locked!\n";
|
||
}
|
||
else {
|
||
print "Ticket is not locked!\n";
|
||
}
|
||
|
||
=cut
|
||
|
||
sub TicketLockGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# check lock state
|
||
return 1 if lc $Ticket{Lock} eq 'lock';
|
||
|
||
return;
|
||
}
|
||
|
||
=head2 TicketLockSet()
|
||
|
||
to lock or unlock a ticket
|
||
|
||
my $Success = $TicketObject->TicketLockSet(
|
||
Lock => 'lock',
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketLockSet(
|
||
LockID => 1,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Optional attribute:
|
||
SendNoNotification, disable or enable agent and customer notification for this
|
||
action. Otherwise a notification will be sent to agent and customer.
|
||
|
||
For example:
|
||
|
||
SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
|
||
|
||
Events:
|
||
TicketLockUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketLockSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# lookup!
|
||
if ( !$Param{LockID} && $Param{Lock} ) {
|
||
|
||
$Param{LockID} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
|
||
Lock => $Param{Lock},
|
||
);
|
||
}
|
||
if ( $Param{LockID} && !$Param{Lock} ) {
|
||
|
||
$Param{Lock} = $Kernel::OM->Get('Kernel::System::Lock')->LockLookup(
|
||
LockID => $Param{LockID},
|
||
);
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID LockID Lock)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if ( !$Param{Lock} && !$Param{LockID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need LockID or Lock!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if update is needed
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
return 1 if $Ticket{Lock} eq $Param{Lock};
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET ticket_lock_id = ?, '
|
||
. ' change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{LockID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
my $HistoryType = '';
|
||
if ( lc $Param{Lock} eq 'unlock' ) {
|
||
$HistoryType = 'Unlock';
|
||
}
|
||
elsif ( lc $Param{Lock} eq 'lock' ) {
|
||
$HistoryType = 'Lock';
|
||
}
|
||
else {
|
||
$HistoryType = 'Misc';
|
||
}
|
||
if ($HistoryType) {
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => $HistoryType,
|
||
Name => "\%\%$Param{Lock}",
|
||
);
|
||
}
|
||
|
||
# set unlock time it event is 'lock'
|
||
if ( $Param{Lock} eq 'lock' ) {
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||
|
||
$Self->TicketUnlockTimeoutUpdate(
|
||
UnlockTimeout => $DateTimeObject->ToEpoch(),
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# send unlock notify
|
||
if ( lc $Param{Lock} eq 'unlock' ) {
|
||
|
||
my $Notification = defined $Param{Notification} ? $Param{Notification} : 1;
|
||
if ( !$Param{SendNoNotification} && $Notification )
|
||
{
|
||
my @SkipRecipients;
|
||
if ( $Ticket{OwnerID} eq $Param{UserID} ) {
|
||
@SkipRecipients = [ $Param{UserID} ];
|
||
}
|
||
|
||
# trigger notification event
|
||
$Self->EventHandler(
|
||
Event => 'NotificationLockTimeout',
|
||
SkipRecipients => \@SkipRecipients,
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
CustomerMessageParams => {},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
}
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketLockUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketArchiveFlagSet()
|
||
|
||
to set the ticket archive flag
|
||
|
||
my $Success = $TicketObject->TicketArchiveFlagSet(
|
||
ArchiveFlag => 'y', # (y|n)
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketArchiveFlagUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketArchiveFlagSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID ArchiveFlag)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# return if feature is not enabled
|
||
return if !$ConfigObject->Get('Ticket::ArchiveSystem');
|
||
|
||
# check given archive flag
|
||
if ( $Param{ArchiveFlag} ne 'y' && $Param{ArchiveFlag} ne 'n' ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "ArchiveFlag is invalid '$Param{ArchiveFlag}'!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if update is needed
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# return if no update is needed
|
||
return 1 if $Ticket{ArchiveFlag} && $Ticket{ArchiveFlag} eq $Param{ArchiveFlag};
|
||
|
||
# translate archive flag
|
||
my $ArchiveFlag = $Param{ArchiveFlag} eq 'y' ? 1 : 0;
|
||
|
||
# set new archive flag
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => '
|
||
UPDATE ticket
|
||
SET archive_flag = ?, change_time = current_timestamp, change_by = ?
|
||
WHERE id = ?',
|
||
Bind => [ \$ArchiveFlag, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# Remove seen flags from ticket and article and ticket watcher data if configured
|
||
# and if the ticket flag was just set.
|
||
if ($ArchiveFlag) {
|
||
|
||
if ( $ConfigObject->Get('Ticket::ArchiveSystem::RemoveSeenFlags') ) {
|
||
$Self->TicketFlagDelete(
|
||
TicketID => $Param{TicketID},
|
||
Key => 'Seen',
|
||
AllUsers => 1,
|
||
);
|
||
|
||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||
|
||
my @Articles = $ArticleObject->ArticleList( TicketID => $Param{TicketID} );
|
||
for my $Article (@Articles) {
|
||
$ArticleObject->ArticleFlagDelete(
|
||
TicketID => $Param{TicketID},
|
||
ArticleID => $Article->{ArticleID},
|
||
Key => 'Seen',
|
||
AllUsers => 1,
|
||
);
|
||
}
|
||
}
|
||
|
||
if (
|
||
$ConfigObject->Get('Ticket::ArchiveSystem::RemoveTicketWatchers')
|
||
&& $ConfigObject->Get('Ticket::Watcher')
|
||
)
|
||
{
|
||
$Self->TicketWatchUnsubscribe(
|
||
TicketID => $Param{TicketID},
|
||
AllUsers => 1,
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
}
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'ArchiveFlagUpdate',
|
||
Name => "\%\%$Param{ArchiveFlag}",
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketArchiveFlagUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketArchiveFlagGet()
|
||
|
||
check if a ticket is archived or not
|
||
|
||
if ( $TicketObject->TicketArchiveFlagGet( TicketID => 123 ) ) {
|
||
print "Ticket is archived!\n";
|
||
}
|
||
else {
|
||
print "Ticket is not archived!\n";
|
||
}
|
||
|
||
=cut
|
||
|
||
sub TicketArchiveFlagGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# check archive state
|
||
return 1 if lc $Ticket{ArchiveFlag} eq 'y';
|
||
|
||
return;
|
||
}
|
||
|
||
=head2 TicketStateSet()
|
||
|
||
to set a ticket state
|
||
|
||
my $Success = $TicketObject->TicketStateSet(
|
||
State => 'open',
|
||
TicketID => 123,
|
||
ArticleID => 123, #optional, for history
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketStateSet(
|
||
StateID => 3,
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Optional attribute:
|
||
SendNoNotification, disable or enable agent and customer notification for this
|
||
action. Otherwise a notification will be sent to agent and customer.
|
||
|
||
For example:
|
||
|
||
SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
|
||
|
||
Events:
|
||
TicketStateUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketStateSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my %State;
|
||
my $ArticleID = $Param{ArticleID} || '';
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if ( !$Param{State} && !$Param{StateID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need StateID or State!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get state object
|
||
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
|
||
|
||
# state id lookup
|
||
if ( !$Param{StateID} ) {
|
||
%State = $StateObject->StateGet( Name => $Param{State} );
|
||
}
|
||
|
||
# state lookup
|
||
if ( !$Param{State} ) {
|
||
%State = $StateObject->StateGet( ID => $Param{StateID} );
|
||
}
|
||
if ( !%State ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need StateID or State!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if update is needed
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
DynamicFields => 0,
|
||
);
|
||
if ( $State{Name} eq $Ticket{State} ) {
|
||
|
||
# update is not needed
|
||
return 1;
|
||
}
|
||
|
||
# permission check
|
||
my %StateList = $Self->StateList(%Param);
|
||
if ( !$StateList{ $State{ID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID} / StateID: $State{ID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET ticket_state_id = ?, '
|
||
. ' change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$State{ID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
StateID => $State{ID},
|
||
ArticleID => $ArticleID,
|
||
QueueID => $Ticket{QueueID},
|
||
Name => "\%\%$Ticket{State}\%\%$State{Name}\%\%",
|
||
HistoryType => 'StateUpdate',
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# trigger event, OldTicketData is needed for escalation events
|
||
$Self->EventHandler(
|
||
Event => 'TicketStateUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
OldTicketData => \%Ticket,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketStateList()
|
||
|
||
to get the state list for a ticket (depends on workflow, if configured)
|
||
|
||
my %States = $TicketObject->TicketStateList(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %States = $TicketObject->TicketStateList(
|
||
TicketID => 123,
|
||
CustomerUserID => 'customer_user_id_123',
|
||
);
|
||
|
||
my %States = $TicketObject->TicketStateList(
|
||
QueueID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %States = $TicketObject->TicketStateList(
|
||
TicketID => 123,
|
||
Type => 'open',
|
||
UserID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
%States = (
|
||
1 => 'State A',
|
||
2 => 'State B',
|
||
3 => 'State C',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketStateList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my %States;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need UserID or CustomerUserID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} && !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need QueueID, TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# get state object
|
||
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
|
||
|
||
# get states by type
|
||
if ( $Param{Type} ) {
|
||
%States = $StateObject->StateGetStatesByType(
|
||
Type => $Param{Type},
|
||
Result => 'HASH',
|
||
);
|
||
}
|
||
elsif ( $Param{Action} ) {
|
||
|
||
if (
|
||
ref $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} ne
|
||
'ARRAY'
|
||
)
|
||
{
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need config for Ticket::Frontend::$Param{Action}->StateType!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
my @StateType = @{ $ConfigObject->Get("Ticket::Frontend::$Param{Action}")->{StateType} };
|
||
%States = $StateObject->StateGetStatesByType(
|
||
StateType => \@StateType,
|
||
Result => 'HASH',
|
||
);
|
||
}
|
||
|
||
# get whole states list
|
||
else {
|
||
%States = $StateObject->StateList(
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'State',
|
||
Data => \%States,
|
||
);
|
||
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %States;
|
||
}
|
||
|
||
=head2 OwnerCheck()
|
||
|
||
to get the ticket owner
|
||
|
||
my ($OwnerID, $Owner) = $TicketObject->OwnerCheck(
|
||
TicketID => 123,
|
||
);
|
||
|
||
or for access control
|
||
|
||
my $AccessOk = $TicketObject->OwnerCheck(
|
||
TicketID => 123,
|
||
OwnerID => 321,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub OwnerCheck {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $SQL = '';
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
if ( $Param{OwnerID} ) {
|
||
|
||
# create cache key
|
||
my $CacheKey = $Param{TicketID} . '::' . $Param{OwnerID};
|
||
|
||
# check cache
|
||
if ( defined $Self->{OwnerCheck}->{$CacheKey} ) {
|
||
return if !$Self->{OwnerCheck}->{$CacheKey};
|
||
return 1 if $Self->{OwnerCheck}->{$CacheKey};
|
||
}
|
||
|
||
# check if user has access
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT user_id FROM ticket WHERE '
|
||
. ' id = ? AND (user_id = ? OR responsible_user_id = ?)',
|
||
Bind => [ \$Param{TicketID}, \$Param{OwnerID}, \$Param{OwnerID}, ],
|
||
);
|
||
my $Access = 0;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Access = 1;
|
||
}
|
||
|
||
# fill cache
|
||
$Self->{OwnerCheck}->{$CacheKey} = $Access;
|
||
return if !$Access;
|
||
return 1 if $Access;
|
||
}
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# search for owner_id and owner
|
||
return if !$DBObject->Prepare(
|
||
SQL => "SELECT st.user_id, su.$ConfigObject->{DatabaseUserTableUser} "
|
||
. " FROM ticket st, $ConfigObject->{DatabaseUserTable} su "
|
||
. " WHERE st.id = ? AND "
|
||
. " st.user_id = su.$ConfigObject->{DatabaseUserTableUserID}",
|
||
Bind => [ \$Param{TicketID}, ],
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Param{SearchUserID} = $Row[0];
|
||
$Param{SearchUser} = $Row[1];
|
||
}
|
||
|
||
# return if no owner as been found
|
||
return if !$Param{SearchUserID};
|
||
|
||
# return owner id and owner
|
||
return $Param{SearchUserID}, $Param{SearchUser};
|
||
}
|
||
|
||
=head2 TicketOwnerSet()
|
||
|
||
to set the ticket owner (notification to the new owner will be sent)
|
||
|
||
by using user id
|
||
|
||
my $Success = $TicketObject->TicketOwnerSet(
|
||
TicketID => 123,
|
||
NewUserID => 555,
|
||
UserID => 123,
|
||
);
|
||
|
||
by using user login
|
||
|
||
my $Success = $TicketObject->TicketOwnerSet(
|
||
TicketID => 123,
|
||
NewUser => 'some-user-login',
|
||
UserID => 123,
|
||
);
|
||
|
||
Return:
|
||
1 = owner has been set
|
||
2 = this owner is already set, no update needed
|
||
|
||
Optional attribute:
|
||
SendNoNotification, disable or enable agent and customer notification for this
|
||
action. Otherwise a notification will be sent to agent and customer.
|
||
|
||
For example:
|
||
|
||
SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
|
||
|
||
Events:
|
||
TicketOwnerUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketOwnerSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if ( !$Param{NewUserID} && !$Param{NewUser} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need NewUserID or NewUser!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# lookup if no NewUserID is given
|
||
if ( !$Param{NewUserID} ) {
|
||
$Param{NewUserID} = $UserObject->UserLookup(
|
||
UserLogin => $Param{NewUser},
|
||
);
|
||
}
|
||
|
||
# lookup if no NewUser is given
|
||
if ( !$Param{NewUser} ) {
|
||
$Param{NewUser} = $UserObject->UserLookup(
|
||
UserID => $Param{NewUserID},
|
||
);
|
||
}
|
||
|
||
# make sure the user exists
|
||
if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "User does not exist.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if update is needed!
|
||
my ( $OwnerID, $Owner ) = $Self->OwnerCheck( TicketID => $Param{TicketID} );
|
||
if ( $OwnerID eq $Param{NewUserID} ) {
|
||
|
||
# update is "not" needed!
|
||
return 2;
|
||
}
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET '
|
||
. ' user_id = ?, change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'OwnerUpdate',
|
||
Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}",
|
||
);
|
||
|
||
# send agent notify
|
||
if ( !$Param{SendNoNotification} ) {
|
||
|
||
my @SkipRecipients;
|
||
if ( $Param{UserID} eq $Param{NewUserID} ) {
|
||
@SkipRecipients = [ $Param{UserID} ];
|
||
}
|
||
|
||
# trigger notification event
|
||
$Self->EventHandler(
|
||
Event => 'NotificationOwnerUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
SkipRecipients => \@SkipRecipients,
|
||
CustomerMessageParams => {
|
||
%Param,
|
||
Body => $Param{Comment} || '',
|
||
},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketOwnerUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketOwnerList()
|
||
|
||
returns the owner in the past as array with hash ref of the owner data
|
||
(name, email, ...)
|
||
|
||
my @Owner = $TicketObject->TicketOwnerList(
|
||
TicketID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
@Owner = (
|
||
{
|
||
UserFirstname => 'SomeName',
|
||
UserLastname => 'SomeName',
|
||
UserEmail => 'some@example.com',
|
||
# custom attributes
|
||
},
|
||
{
|
||
UserFirstname => 'SomeName',
|
||
UserLastname => 'SomeName',
|
||
UserEmail => 'some@example.com',
|
||
# custom attributes
|
||
},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketOwnerList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketID!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT sh.owner_id FROM ticket_history sh, ticket_history_type ht WHERE '
|
||
. ' sh.ticket_id = ? AND ht.name IN (\'OwnerUpdate\', \'NewTicket\') AND '
|
||
. ' ht.id = sh.history_type_id ORDER BY sh.id',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
my @UserID;
|
||
|
||
USER:
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
next USER if !$Row[0];
|
||
next USER if $Row[0] eq 1;
|
||
push @UserID, $Row[0];
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
my @UserInfo;
|
||
USER:
|
||
for my $UserID (@UserID) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $UserID,
|
||
Cache => 1,
|
||
Valid => 1,
|
||
);
|
||
|
||
next USER if !%User;
|
||
|
||
push @UserInfo, \%User;
|
||
}
|
||
|
||
return @UserInfo;
|
||
}
|
||
|
||
=head2 TicketResponsibleSet()
|
||
|
||
to set the ticket responsible (notification to the new responsible will be sent)
|
||
|
||
by using user id
|
||
|
||
my $Success = $TicketObject->TicketResponsibleSet(
|
||
TicketID => 123,
|
||
NewUserID => 555,
|
||
UserID => 213,
|
||
);
|
||
|
||
by using user login
|
||
|
||
my $Success = $TicketObject->TicketResponsibleSet(
|
||
TicketID => 123,
|
||
NewUser => 'some-user-login',
|
||
UserID => 213,
|
||
);
|
||
|
||
Return:
|
||
1 = responsible has been set
|
||
2 = this responsible is already set, no update needed
|
||
|
||
Optional attribute:
|
||
SendNoNotification, disable or enable agent and customer notification for this
|
||
action. Otherwise a notification will be sent to agent and customer.
|
||
|
||
For example:
|
||
|
||
SendNoNotification => 0, # optional 1|0 (send no agent and customer notification)
|
||
|
||
Events:
|
||
TicketResponsibleUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketResponsibleSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if ( !$Param{NewUserID} && !$Param{NewUser} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need NewUserID or NewUser!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# lookup if no NewUserID is given
|
||
if ( !$Param{NewUserID} ) {
|
||
$Param{NewUserID} = $UserObject->UserLookup( UserLogin => $Param{NewUser} );
|
||
}
|
||
|
||
# lookup if no NewUser is given
|
||
if ( !$Param{NewUser} ) {
|
||
$Param{NewUser} = $UserObject->UserLookup( UserID => $Param{NewUserID} );
|
||
}
|
||
|
||
# make sure the user exists
|
||
if ( !$UserObject->UserLookup( UserID => $Param{NewUserID} ) ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "User does not exist.",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if update is needed!
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{NewUserID},
|
||
DynamicFields => 0,
|
||
);
|
||
if ( $Ticket{ResponsibleID} eq $Param{NewUserID} ) {
|
||
|
||
# update is "not" needed!
|
||
return 2;
|
||
}
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET responsible_user_id = ?, '
|
||
. ' change_time = current_timestamp, change_by = ? '
|
||
. ' WHERE id = ?',
|
||
Bind => [ \$Param{NewUserID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'ResponsibleUpdate',
|
||
Name => "\%\%$Param{NewUser}\%\%$Param{NewUserID}",
|
||
);
|
||
|
||
# send agent notify
|
||
if ( !$Param{SendNoNotification} ) {
|
||
|
||
my @SkipRecipients;
|
||
if ( $Param{UserID} eq $Param{NewUserID} ) {
|
||
@SkipRecipients = [ $Param{UserID} ];
|
||
}
|
||
|
||
# trigger notification event
|
||
$Self->EventHandler(
|
||
Event => 'NotificationResponsibleUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
SkipRecipients => \@SkipRecipients,
|
||
CustomerMessageParams => \%Param,
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketResponsibleUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketResponsibleList()
|
||
|
||
returns the responsible in the past as array with hash ref of the owner data
|
||
(name, email, ...)
|
||
|
||
my @Responsible = $TicketObject->TicketResponsibleList(
|
||
TicketID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
@Responsible = (
|
||
{
|
||
UserFirstname => 'SomeName',
|
||
UserLastname => 'SomeName',
|
||
UserEmail => 'some@example.com',
|
||
# custom attributes
|
||
},
|
||
{
|
||
UserFirstname => 'SomeName',
|
||
UserLastname => 'SomeName',
|
||
UserEmail => 'some@example.com',
|
||
# custom attributes
|
||
},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketResponsibleList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketID!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
my @User;
|
||
my $LastResponsible = 1;
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT sh.name, ht.name, sh.create_by FROM '
|
||
. ' ticket_history sh, ticket_history_type ht WHERE '
|
||
. ' sh.ticket_id = ? AND '
|
||
. ' ht.name IN (\'ResponsibleUpdate\', \'NewTicket\') AND '
|
||
. ' ht.id = sh.history_type_id ORDER BY sh.id',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
|
||
# store result
|
||
if ( $Row[1] eq 'NewTicket' && $Row[2] ne '1' && $LastResponsible ne $Row[2] ) {
|
||
$LastResponsible = $Row[2];
|
||
push @User, $Row[2];
|
||
}
|
||
elsif ( $Row[1] eq 'ResponsibleUpdate' ) {
|
||
if (
|
||
$Row[0] =~ /^New Responsible is '(.+?)' \(ID=(.+?)\)/
|
||
|| $Row[0] =~ /^\%\%(.+?)\%\%(.+?)$/
|
||
)
|
||
{
|
||
$LastResponsible = $2;
|
||
push @User, $2;
|
||
}
|
||
}
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
my @UserInfo;
|
||
for my $SingleUser (@User) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $SingleUser,
|
||
Cache => 1
|
||
);
|
||
push @UserInfo, \%User;
|
||
}
|
||
|
||
return @UserInfo;
|
||
}
|
||
|
||
=head2 TicketInvolvedAgentsList()
|
||
|
||
returns an array with hash ref of agents which have been involved with a ticket.
|
||
It is guaranteed that no agent is returned twice.
|
||
|
||
my @InvolvedAgents = $TicketObject->TicketInvolvedAgentsList(
|
||
TicketID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
@InvolvedAgents = (
|
||
{
|
||
UserFirstname => 'SomeName',
|
||
UserLastname => 'SomeName',
|
||
UserEmail => 'some@example.com',
|
||
# custom attributes
|
||
},
|
||
{
|
||
UserFirstname => 'AnotherName',
|
||
UserLastname => 'AnotherName',
|
||
UserEmail => 'another@example.com',
|
||
# custom attributes
|
||
},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketInvolvedAgentsList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query, only entries with a known history_id are retrieved
|
||
my @User;
|
||
my %UsedOwner;
|
||
return if !$DBObject->Prepare(
|
||
SQL => ''
|
||
. 'SELECT sh.create_by'
|
||
. ' FROM ticket_history sh, ticket_history_type ht'
|
||
. ' WHERE sh.ticket_id = ?'
|
||
. ' AND ht.id = sh.history_type_id'
|
||
. ' ORDER BY sh.id',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
|
||
# store result, skip the
|
||
if ( $Row[0] ne 1 && !$UsedOwner{ $Row[0] } ) {
|
||
$UsedOwner{ $Row[0] } = $Row[0];
|
||
push @User, $Row[0];
|
||
}
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# collect agent information
|
||
my @UserInfo;
|
||
USER:
|
||
for my $SingleUser (@User) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $SingleUser,
|
||
Valid => 1,
|
||
Cache => 1,
|
||
);
|
||
|
||
next USER if !%User;
|
||
|
||
push @UserInfo, \%User;
|
||
}
|
||
|
||
return @UserInfo;
|
||
}
|
||
|
||
=head2 TicketPrioritySet()
|
||
|
||
to set the ticket priority
|
||
|
||
my $Success = $TicketObject->TicketPrioritySet(
|
||
TicketID => 123,
|
||
Priority => 'low',
|
||
UserID => 213,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketPrioritySet(
|
||
TicketID => 123,
|
||
PriorityID => 2,
|
||
UserID => 213,
|
||
);
|
||
|
||
Events:
|
||
TicketPriorityUpdate
|
||
|
||
=cut
|
||
|
||
sub TicketPrioritySet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# get priority object
|
||
my $PriorityObject = $Kernel::OM->Get('Kernel::System::Priority');
|
||
|
||
# lookup!
|
||
if ( !$Param{PriorityID} && $Param{Priority} ) {
|
||
$Param{PriorityID} = $PriorityObject->PriorityLookup(
|
||
Priority => $Param{Priority},
|
||
);
|
||
}
|
||
if ( $Param{PriorityID} && !$Param{Priority} ) {
|
||
$Param{Priority} = $PriorityObject->PriorityLookup(
|
||
PriorityID => $Param{PriorityID},
|
||
);
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID PriorityID Priority)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
my %Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
# check if update is needed
|
||
if ( $Ticket{Priority} eq $Param{Priority} ) {
|
||
|
||
# update not needed
|
||
return 1;
|
||
}
|
||
|
||
# permission check
|
||
my %PriorityList = $Self->PriorityList(%Param);
|
||
if ( !$PriorityList{ $Param{PriorityID} } ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "Permission denied on TicketID: $Param{TicketID}!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# db update
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'UPDATE ticket SET ticket_priority_id = ?, '
|
||
. ' change_time = current_timestamp, change_by = ?'
|
||
. ' WHERE id = ?',
|
||
Bind => [ \$Param{PriorityID}, \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
QueueID => $Ticket{QueueID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'PriorityUpdate',
|
||
Name => "\%\%$Ticket{Priority}\%\%$Ticket{PriorityID}"
|
||
. "\%\%$Param{Priority}\%\%$Param{PriorityID}",
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketPriorityUpdate',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketPriorityList()
|
||
|
||
to get the priority list for a ticket (depends on workflow, if configured)
|
||
|
||
my %Priorities = $TicketObject->TicketPriorityList(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
my %Priorities = $TicketObject->TicketPriorityList(
|
||
TicketID => 123,
|
||
CustomerUserID => 'customer_user_id_123',
|
||
);
|
||
|
||
my %Priorities = $TicketObject->TicketPriorityList(
|
||
QueueID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Returns:
|
||
|
||
%Priorities = (
|
||
1 => 'Priority A',
|
||
2 => 'Priority B',
|
||
3 => 'Priority C',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketPriorityList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{UserID} && !$Param{CustomerUserID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need UserID or CustomerUserID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
my %Data = $Kernel::OM->Get('Kernel::System::Priority')->PriorityList(%Param);
|
||
|
||
# workflow
|
||
my $ACL = $Self->TicketAcl(
|
||
%Param,
|
||
ReturnType => 'Ticket',
|
||
ReturnSubType => 'Priority',
|
||
Data => \%Data,
|
||
);
|
||
|
||
return $Self->TicketAclData() if $ACL;
|
||
return %Data;
|
||
}
|
||
|
||
=head2 HistoryTicketStatusGet()
|
||
|
||
get a hash with ticket id as key and a hash ref (result of HistoryTicketGet)
|
||
of all affected tickets in this time area.
|
||
|
||
my %Tickets = $TicketObject->HistoryTicketStatusGet(
|
||
StartDay => 12,
|
||
StartMonth => 1,
|
||
StartYear => 2006,
|
||
StopDay => 18,
|
||
StopMonth => 1,
|
||
StopYear => 2006,
|
||
Force => 0,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub HistoryTicketStatusGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(StopYear StopMonth StopDay StartYear StartMonth StartDay)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# format month and day params
|
||
for my $DateParameter (qw(StopMonth StopDay StartMonth StartDay)) {
|
||
$Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} );
|
||
}
|
||
|
||
my $SQLExt = '';
|
||
for my $HistoryTypeData (
|
||
qw(NewTicket FollowUp OwnerUpdate PriorityUpdate CustomerUpdate StateUpdate
|
||
PhoneCallCustomer Forward Bounce SendAnswer EmailCustomer
|
||
PhoneCallAgent WebRequestCustomer TicketDynamicFieldUpdate)
|
||
)
|
||
{
|
||
my $ID = $Self->HistoryTypeLookup( Type => $HistoryTypeData );
|
||
if ( !$SQLExt ) {
|
||
$SQLExt = "AND history_type_id IN ($ID";
|
||
}
|
||
else {
|
||
$SQLExt .= ",$ID";
|
||
}
|
||
}
|
||
|
||
if ($SQLExt) {
|
||
$SQLExt .= ')';
|
||
}
|
||
|
||
# assemble stop date/time string for database comparison
|
||
my $StopDateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} 00:00:00",
|
||
}
|
||
);
|
||
$StopDateTimeObject->Add( Hours => 24 );
|
||
my $StopDateTimeString = $StopDateTimeObject->Format( Format => '%Y-%m-%d 00:00:00' );
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
return if !$DBObject->Prepare(
|
||
SQL => "
|
||
SELECT DISTINCT(th.ticket_id), th.create_time
|
||
FROM ticket_history th
|
||
WHERE th.create_time <= '$StopDateTimeString'
|
||
AND th.create_time >= '$Param{StartYear}-$Param{StartMonth}-$Param{StartDay} 00:00:01'
|
||
$SQLExt
|
||
ORDER BY th.create_time DESC",
|
||
Limit => 150000,
|
||
);
|
||
|
||
my %Ticket;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Ticket{ $Row[0] } = 1;
|
||
}
|
||
|
||
for my $TicketID ( sort keys %Ticket ) {
|
||
|
||
my %TicketData = $Self->HistoryTicketGet(
|
||
TicketID => $TicketID,
|
||
StopYear => $Param{StopYear},
|
||
StopMonth => $Param{StopMonth},
|
||
StopDay => $Param{StopDay},
|
||
Force => $Param{Force} || 0,
|
||
);
|
||
|
||
if (%TicketData) {
|
||
$Ticket{$TicketID} = \%TicketData;
|
||
}
|
||
else {
|
||
$Ticket{$TicketID} = {};
|
||
}
|
||
}
|
||
|
||
return %Ticket;
|
||
}
|
||
|
||
=head2 HistoryTicketGet()
|
||
|
||
returns a hash of some of the ticket data
|
||
calculated based on ticket history info at the given date.
|
||
|
||
my %HistoryData = $TicketObject->HistoryTicketGet(
|
||
StopYear => 2003,
|
||
StopMonth => 12,
|
||
StopDay => 24,
|
||
StopHour => 10, (optional, default 23)
|
||
StopMinute => 0, (optional, default 59)
|
||
StopSecond => 0, (optional, default 59)
|
||
TicketID => 123,
|
||
Force => 0, # 1: don't use cache
|
||
);
|
||
|
||
returns
|
||
|
||
TicketNumber
|
||
TicketID
|
||
Type
|
||
TypeID
|
||
Queue
|
||
QueueID
|
||
Priority
|
||
PriorityID
|
||
State
|
||
StateID
|
||
Owner
|
||
OwnerID
|
||
CreateUserID
|
||
CreateTime (timestamp)
|
||
CreateOwnerID
|
||
CreatePriority
|
||
CreatePriorityID
|
||
CreateState
|
||
CreateStateID
|
||
CreateQueue
|
||
CreateQueueID
|
||
LockFirst (timestamp)
|
||
LockLast (timestamp)
|
||
UnlockFirst (timestamp)
|
||
UnlockLast (timestamp)
|
||
|
||
=cut
|
||
|
||
sub HistoryTicketGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID StopYear StopMonth StopDay)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
$Param{StopHour} = defined $Param{StopHour} ? $Param{StopHour} : '23';
|
||
$Param{StopMinute} = defined $Param{StopMinute} ? $Param{StopMinute} : '59';
|
||
$Param{StopSecond} = defined $Param{StopSecond} ? $Param{StopSecond} : '59';
|
||
|
||
# format month and day params
|
||
for my $DateParameter (qw(StopMonth StopDay)) {
|
||
$Param{$DateParameter} = sprintf( "%02d", $Param{$DateParameter} );
|
||
}
|
||
|
||
my $CacheKey = 'HistoryTicketGet::'
|
||
. join( '::', map { ( $_ || 0 ) . "::$Param{$_}" } sort keys %Param );
|
||
|
||
my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKey,
|
||
);
|
||
if ( ref $Cached eq 'HASH' && !$Param{Force} ) {
|
||
return %{$Cached};
|
||
}
|
||
|
||
my $Time
|
||
= "$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}";
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT th.name, tht.name, th.create_time, th.create_by, th.ticket_id,
|
||
th.article_id, th.queue_id, th.state_id, th.priority_id, th.owner_id, th.type_id
|
||
FROM ticket_history th, ticket_history_type tht
|
||
WHERE th.history_type_id = tht.id
|
||
AND th.ticket_id = ?
|
||
AND th.create_time <= ?
|
||
ORDER BY th.create_time, th.id ASC',
|
||
Bind => [ \$Param{TicketID}, \$Time ],
|
||
Limit => 3000,
|
||
);
|
||
|
||
my %Ticket;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
|
||
if ( $Row[1] eq 'NewTicket' ) {
|
||
if (
|
||
$Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/
|
||
|| $Row[0] =~ /Ticket=\[(.+?)\],.+?Q\=(.+?);P\=(.+?);S\=(.+?)/
|
||
)
|
||
{
|
||
$Ticket{TicketNumber} = $1;
|
||
$Ticket{Queue} = $2;
|
||
$Ticket{CreateQueue} = $2;
|
||
$Ticket{Priority} = $3;
|
||
$Ticket{CreatePriority} = $3;
|
||
$Ticket{State} = $4;
|
||
$Ticket{CreateState} = $4;
|
||
$Ticket{TicketID} = $Row[4];
|
||
$Ticket{Owner} = 'root';
|
||
$Ticket{CreateUserID} = $Row[3];
|
||
$Ticket{CreateTime} = $Row[2];
|
||
}
|
||
else {
|
||
|
||
# COMPAT: compat to 1.1
|
||
# NewTicket
|
||
$Ticket{TicketVersion} = '1.1';
|
||
$Ticket{TicketID} = $Row[4];
|
||
$Ticket{CreateUserID} = $Row[3];
|
||
$Ticket{CreateTime} = $Row[2];
|
||
}
|
||
$Ticket{CreateOwnerID} = $Row[9] || '';
|
||
$Ticket{CreatePriorityID} = $Row[8] || '';
|
||
$Ticket{CreateStateID} = $Row[7] || '';
|
||
$Ticket{CreateQueueID} = $Row[6] || '';
|
||
}
|
||
|
||
# COMPAT: compat to 1.1
|
||
elsif ( $Row[1] eq 'PhoneCallCustomer' ) {
|
||
$Ticket{TicketVersion} = '1.1';
|
||
$Ticket{TicketID} = $Row[4];
|
||
$Ticket{CreateUserID} = $Row[3];
|
||
$Ticket{CreateTime} = $Row[2];
|
||
}
|
||
elsif ( $Row[1] eq 'Move' ) {
|
||
if (
|
||
$Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/
|
||
|| $Row[0] =~ /^Ticket moved to Queue '(.+?)'/
|
||
)
|
||
{
|
||
$Ticket{Queue} = $1;
|
||
}
|
||
}
|
||
elsif (
|
||
$Row[1] eq 'StateUpdate'
|
||
|| $Row[1] eq 'Close successful'
|
||
|| $Row[1] eq 'Close unsuccessful'
|
||
|| $Row[1] eq 'Open'
|
||
|| $Row[1] eq 'Misc'
|
||
)
|
||
{
|
||
if (
|
||
$Row[0] =~ /^\%\%(.+?)\%\%(.+?)(\%\%|)$/
|
||
|| $Row[0] =~ /^Old: '(.+?)' New: '(.+?)'/
|
||
|| $Row[0] =~ /^Changed Ticket State from '(.+?)' to '(.+?)'/
|
||
)
|
||
{
|
||
$Ticket{State} = $2;
|
||
$Ticket{StateTime} = $Row[2];
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'TicketFreeTextUpdate' ) {
|
||
if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)$/ ) {
|
||
$Ticket{ 'Ticket' . $1 } = $2;
|
||
$Ticket{ 'Ticket' . $3 } = $4;
|
||
$Ticket{$1} = $2;
|
||
$Ticket{$3} = $4;
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'TicketDynamicFieldUpdate' ) {
|
||
|
||
# take care about different values between 3.3 and 4
|
||
# 3.x: %%FieldName%%test%%Value%%TestValue1
|
||
# 4.x: %%FieldName%%test%%Value%%TestValue1%%OldValue%%OldTestValue1
|
||
if ( $Row[0] =~ /^\%\%FieldName\%\%(.+?)\%\%Value\%\%(.*?)(?:\%\%|$)/ ) {
|
||
|
||
my $FieldName = $1;
|
||
my $Value = $2 || '';
|
||
$Ticket{$FieldName} = $Value;
|
||
|
||
# Backward compatibility for TicketFreeText and TicketFreeTime
|
||
if ( $FieldName =~ /^Ticket(Free(?:Text|Key)(?:[?:1[0-6]|[1-9]))$/ ) {
|
||
|
||
# Remove the leading Ticket on field name
|
||
my $FreeFieldName = $1;
|
||
$Ticket{$FreeFieldName} = $Value;
|
||
}
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'PriorityUpdate' ) {
|
||
if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)\%\%(.+?)\%\%(.+?)/ ) {
|
||
$Ticket{Priority} = $3;
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'OwnerUpdate' ) {
|
||
if ( $Row[0] =~ /^\%\%(.+?)\%\%(.+?)/ || $Row[0] =~ /^New Owner is '(.+?)'/ ) {
|
||
$Ticket{Owner} = $1;
|
||
}
|
||
}
|
||
elsif ( $Row[1] eq 'Lock' ) {
|
||
if ( !$Ticket{LockFirst} ) {
|
||
$Ticket{LockFirst} = $Row[2];
|
||
}
|
||
$Ticket{LockLast} = $Row[2];
|
||
}
|
||
elsif ( $Row[1] eq 'Unlock' ) {
|
||
if ( !$Ticket{UnlockFirst} ) {
|
||
$Ticket{UnlockFirst} = $Row[2];
|
||
}
|
||
$Ticket{UnlockLast} = $Row[2];
|
||
}
|
||
|
||
# get default options
|
||
$Ticket{TypeID} = $Row[10] || '';
|
||
$Ticket{OwnerID} = $Row[9] || '';
|
||
$Ticket{PriorityID} = $Row[8] || '';
|
||
$Ticket{StateID} = $Row[7] || '';
|
||
$Ticket{QueueID} = $Row[6] || '';
|
||
}
|
||
if ( !%Ticket ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'notice',
|
||
Message => "No such TicketID in ticket history till "
|
||
. "'$Param{StopYear}-$Param{StopMonth}-$Param{StopDay} $Param{StopHour}:$Param{StopMinute}:$Param{StopSecond}' ($Param{TicketID})!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# update old ticket info
|
||
my %CurrentTicketData = $Self->TicketGet(
|
||
TicketID => $Ticket{TicketID},
|
||
DynamicFields => 0,
|
||
);
|
||
for my $TicketAttribute (qw(State Priority Queue TicketNumber)) {
|
||
if ( !$Ticket{$TicketAttribute} ) {
|
||
$Ticket{$TicketAttribute} = $CurrentTicketData{$TicketAttribute};
|
||
}
|
||
if ( !$Ticket{"Create$TicketAttribute"} ) {
|
||
$Ticket{"Create$TicketAttribute"} = $CurrentTicketData{$TicketAttribute};
|
||
}
|
||
}
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||
|
||
# check if we should cache this ticket data
|
||
my $DateTimeValues = $DateTimeObject->Get();
|
||
|
||
# if the request is for the last month or older, cache it
|
||
if ( int $DateTimeValues->{Year} . int $DateTimeValues->{Month} > int $Param{StopYear} . int $Param{StopMonth} ) {
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
||
Type => $Self->{CacheType},
|
||
TTL => $Self->{CacheTTL},
|
||
Key => $CacheKey,
|
||
Value => \%Ticket,
|
||
);
|
||
}
|
||
|
||
return %Ticket;
|
||
}
|
||
|
||
=head2 HistoryTypeLookup()
|
||
|
||
returns the id of the requested history type.
|
||
|
||
my $ID = $TicketObject->HistoryTypeLookup( Type => 'Move' );
|
||
|
||
=cut
|
||
|
||
sub HistoryTypeLookup {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{Type} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need Type!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if we ask the same request?
|
||
my $CacheKey = 'Ticket::History::HistoryTypeLookup::' . $Param{Type};
|
||
my $Cached = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKey,
|
||
);
|
||
|
||
if ($Cached) {
|
||
return $Cached;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT id FROM ticket_history_type WHERE name = ?',
|
||
Bind => [ \$Param{Type} ],
|
||
);
|
||
|
||
my $HistoryTypeID;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$HistoryTypeID = $Row[0];
|
||
}
|
||
|
||
# check if data exists
|
||
if ( !$HistoryTypeID ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No TypeID for $Param{Type} found!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# set cache
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
||
Type => $Self->{CacheType},
|
||
TTL => $Self->{CacheTTL},
|
||
Key => $CacheKey,
|
||
Value => $HistoryTypeID,
|
||
CacheInMemory => 1,
|
||
CacheInBackend => 0,
|
||
);
|
||
|
||
return $HistoryTypeID;
|
||
}
|
||
|
||
=head2 HistoryAdd()
|
||
|
||
add a history entry to an ticket
|
||
|
||
my $Success = $TicketObject->HistoryAdd(
|
||
Name => 'Some Comment',
|
||
HistoryType => 'Move', # see system tables
|
||
TicketID => 123,
|
||
ArticleID => 1234, # not required!
|
||
QueueID => 123, # not required!
|
||
TypeID => 123, # not required!
|
||
CreateUserID => 123,
|
||
);
|
||
|
||
Events:
|
||
HistoryAdd
|
||
|
||
=cut
|
||
|
||
sub HistoryAdd {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{Name} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need Name!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# lookup!
|
||
if ( !$Param{HistoryTypeID} && $Param{HistoryType} ) {
|
||
$Param{HistoryTypeID} = $Self->HistoryTypeLookup( Type => $Param{HistoryType} );
|
||
}
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID CreateUserID HistoryTypeID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
my %Ticket;
|
||
if ( !$Param{QueueID} || !$Param{TypeID} || !$Param{OwnerID} || !$Param{PriorityID} || !$Param{StateID} ) {
|
||
%Ticket = $Self->TicketGet(
|
||
%Param,
|
||
DynamicFields => 0,
|
||
);
|
||
}
|
||
|
||
if ( !$Param{QueueID} ) {
|
||
$Param{QueueID} = $Ticket{QueueID};
|
||
}
|
||
if ( !$Param{TypeID} ) {
|
||
$Param{TypeID} = $Ticket{TypeID};
|
||
}
|
||
if ( !$Param{OwnerID} ) {
|
||
$Param{OwnerID} = $Ticket{OwnerID};
|
||
}
|
||
if ( !$Param{PriorityID} ) {
|
||
$Param{PriorityID} = $Ticket{PriorityID};
|
||
}
|
||
if ( !$Param{StateID} ) {
|
||
$Param{StateID} = $Ticket{StateID};
|
||
}
|
||
|
||
# limit name to 200 chars
|
||
if ( $Param{Name} ) {
|
||
$Param{Name} = substr( $Param{Name}, 0, 200 );
|
||
}
|
||
|
||
# db quote
|
||
if ( !$Param{ArticleID} ) {
|
||
$Param{ArticleID} = undef;
|
||
}
|
||
|
||
# db insert
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'INSERT INTO ticket_history '
|
||
. ' (name, history_type_id, ticket_id, article_id, queue_id, owner_id, '
|
||
. ' priority_id, state_id, type_id, '
|
||
. ' create_time, create_by, change_time, change_by) '
|
||
. 'VALUES '
|
||
. '(?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp, ?, current_timestamp, ?)',
|
||
Bind => [
|
||
\$Param{Name}, \$Param{HistoryTypeID}, \$Param{TicketID}, \$Param{ArticleID},
|
||
\$Param{QueueID}, \$Param{OwnerID}, \$Param{PriorityID}, \$Param{StateID},
|
||
\$Param{TypeID}, \$Param{CreateUserID}, \$Param{CreateUserID},
|
||
],
|
||
);
|
||
|
||
# Prevent infinite loops for notifications base on 'HistoryAdd' event
|
||
# see bug#13002
|
||
if ( $Param{HistoryType} ne 'SendAgentNotification' ) {
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'HistoryAdd',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{CreateUserID},
|
||
);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 HistoryGet()
|
||
|
||
get ticket history as array with hashes
|
||
(TicketID, ArticleID, Name, CreateBy, CreateTime, HistoryType, QueueID,
|
||
OwnerID, PriorityID, StateID, HistoryTypeID and TypeID)
|
||
|
||
my @HistoryLines = $TicketObject->HistoryGet(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub HistoryGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my @Lines;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT sh.name, sh.article_id, sh.create_time, sh.create_by, ht.name, '
|
||
. ' sh.queue_id, sh.owner_id, sh.priority_id, sh.state_id, sh.history_type_id, sh.type_id '
|
||
. ' FROM ticket_history sh, ticket_history_type ht WHERE '
|
||
. ' sh.ticket_id = ? AND ht.id = sh.history_type_id'
|
||
. ' ORDER BY sh.create_time, sh.id',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
my %Data;
|
||
$Data{TicketID} = $Param{TicketID};
|
||
$Data{ArticleID} = $Row[1] || 0;
|
||
$Data{Name} = $Row[0];
|
||
$Data{CreateBy} = $Row[3];
|
||
$Data{CreateTime} = $Row[2];
|
||
$Data{HistoryType} = $Row[4];
|
||
$Data{QueueID} = $Row[5];
|
||
$Data{OwnerID} = $Row[6];
|
||
$Data{PriorityID} = $Row[7];
|
||
$Data{StateID} = $Row[8];
|
||
$Data{HistoryTypeID} = $Row[9];
|
||
$Data{TypeID} = $Row[10];
|
||
push @Lines, \%Data;
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
# get user data
|
||
for my $Data (@Lines) {
|
||
|
||
my %UserInfo = $UserObject->GetUserData(
|
||
UserID => $Data->{CreateBy},
|
||
);
|
||
|
||
# merge result, put %Data last so that it "wins"
|
||
%{$Data} = ( %UserInfo, %{$Data} );
|
||
}
|
||
|
||
return @Lines;
|
||
}
|
||
|
||
=head2 HistoryDelete()
|
||
|
||
delete a ticket history (from storage)
|
||
|
||
my $Success = $TicketObject->HistoryDelete(
|
||
TicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
HistoryDelete
|
||
|
||
=cut
|
||
|
||
sub HistoryDelete {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# delete ticket history entries from db
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL =>
|
||
'DELETE FROM ticket_history WHERE ticket_id = ? AND (article_id IS NULL OR article_id = 0)',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'HistoryDelete',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketAccountedTimeGet()
|
||
|
||
returns the accounted time of a ticket.
|
||
|
||
my $AccountedTime = $TicketObject->TicketAccountedTimeGet(TicketID => 1234);
|
||
|
||
=cut
|
||
|
||
sub TicketAccountedTimeGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT time_unit FROM time_accounting WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
my $AccountedTime = 0;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Row[0] =~ s/,/./g;
|
||
$AccountedTime = $AccountedTime + $Row[0];
|
||
}
|
||
|
||
return $AccountedTime;
|
||
}
|
||
|
||
=head2 TicketAccountTime()
|
||
|
||
account time to a ticket.
|
||
|
||
my $Success = $TicketObject->TicketAccountTime(
|
||
TicketID => 1234,
|
||
ArticleID => 23542,
|
||
TimeUnit => '4.5',
|
||
UserID => 1,
|
||
);
|
||
|
||
Events:
|
||
TicketAccountTime
|
||
|
||
=cut
|
||
|
||
sub TicketAccountTime {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID ArticleID TimeUnit UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# check some wrong formats
|
||
$Param{TimeUnit} =~ s/,/\./g;
|
||
$Param{TimeUnit} =~ s/ //g;
|
||
$Param{TimeUnit} =~ s/^(\d{1,10}\.\d\d).+?$/$1/g;
|
||
chomp $Param{TimeUnit};
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# update change time
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE ticket SET change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Param{TicketID} ],
|
||
);
|
||
|
||
# db quote
|
||
$Param{TimeUnit} = $DBObject->Quote( $Param{TimeUnit}, 'Number' );
|
||
|
||
# db update
|
||
return if !$DBObject->Do(
|
||
SQL => "INSERT INTO time_accounting "
|
||
. " (ticket_id, article_id, time_unit, create_time, create_by, change_time, change_by) "
|
||
. " VALUES (?, ?, $Param{TimeUnit}, current_timestamp, ?, current_timestamp, ?)",
|
||
Bind => [
|
||
\$Param{TicketID}, \$Param{ArticleID}, \$Param{UserID}, \$Param{UserID},
|
||
],
|
||
);
|
||
|
||
# clear ticket cache
|
||
$Self->_TicketCacheClear( TicketID => $Param{TicketID} );
|
||
|
||
# add history
|
||
my $AccountedTime = $Self->TicketAccountedTimeGet( TicketID => $Param{TicketID} );
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
ArticleID => $Param{ArticleID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'TimeAccounting',
|
||
Name => "\%\%$Param{TimeUnit}\%\%$AccountedTime",
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketAccountTime',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
ArticleID => $Param{ArticleID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketMerge()
|
||
|
||
merge two tickets
|
||
|
||
my $Success = $TicketObject->TicketMerge(
|
||
MainTicketID => 412,
|
||
MergeTicketID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketMerge
|
||
|
||
=cut
|
||
|
||
sub TicketMerge {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# Get the list of all merged states.
|
||
my @MergeStateList = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
|
||
StateType => ['merged'],
|
||
Result => 'Name',
|
||
);
|
||
|
||
# Error handling.
|
||
if ( !@MergeStateList ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "No merge state found! Please add a valid merge state.",
|
||
);
|
||
return 'NoValidMergeStates';
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# change ticket id of merge ticket to main ticket
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE article SET ticket_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE ticket_id = ?',
|
||
Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
|
||
);
|
||
|
||
# former bug 9635 (with table article_index)
|
||
# do the same with article_search_index (harmless if not used)
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE article_search_index SET ticket_id = ? WHERE ticket_id = ?',
|
||
Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ],
|
||
);
|
||
|
||
# reassign article history
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE ticket_history SET ticket_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE ticket_id = ?
|
||
AND (article_id IS NOT NULL OR article_id != 0)',
|
||
Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
|
||
);
|
||
|
||
# update the accounted time of the main ticket
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE time_accounting SET ticket_id = ?, change_time = current_timestamp, '
|
||
. ' change_by = ? WHERE ticket_id = ?',
|
||
Bind => [ \$Param{MainTicketID}, \$Param{UserID}, \$Param{MergeTicketID} ],
|
||
);
|
||
|
||
my %MainTicket = $Self->TicketGet(
|
||
TicketID => $Param{MainTicketID},
|
||
DynamicFields => 0,
|
||
);
|
||
my %MergeTicket = $Self->TicketGet(
|
||
TicketID => $Param{MergeTicketID},
|
||
DynamicFields => 0,
|
||
);
|
||
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# Set language for AutomaticMergeText, see more in bug #13967
|
||
my %UserInfo = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
||
UserID => $Param{UserID},
|
||
);
|
||
my $Language = $UserInfo{UserLanguage} || $Kernel::OM->Get('Kernel::Config')->Get('DefaultLanguage') || 'en';
|
||
|
||
$Kernel::OM->ObjectsDiscard(
|
||
Objects => ['Kernel::Language'],
|
||
);
|
||
$Kernel::OM->ObjectParamAdd(
|
||
'Kernel::Language' => {
|
||
UserLanguage => $Language,
|
||
},
|
||
);
|
||
my $LanguageObject = $Kernel::OM->Get('Kernel::Language');
|
||
|
||
my $Body = $ConfigObject->Get('Ticket::Frontend::AutomaticMergeText');
|
||
$Body = $LanguageObject->Translate($Body);
|
||
$Body =~ s{<OTRS_TICKET>}{$MergeTicket{TicketNumber}}xms;
|
||
$Body =~ s{<OTRS_MERGE_TO_TICKET>}{$MainTicket{TicketNumber}}xms;
|
||
|
||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||
|
||
# Add merge article to merge ticket using internal channel.
|
||
$ArticleObject->BackendForChannel( ChannelName => 'Internal' )->ArticleCreate(
|
||
TicketID => $Param{MergeTicketID},
|
||
SenderType => 'agent',
|
||
IsVisibleForCustomer => 1,
|
||
ContentType => "text/plain; charset=ascii",
|
||
UserID => $Param{UserID},
|
||
HistoryType => 'AddNote',
|
||
HistoryComment => '%%Note',
|
||
Subject => $ConfigObject->Get('Ticket::Frontend::AutomaticMergeSubject') || 'Ticket Merged',
|
||
Body => $Body,
|
||
NoAgentNotify => 1,
|
||
);
|
||
|
||
# add merge history to merge ticket
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{MergeTicketID},
|
||
HistoryType => 'Merged',
|
||
Name => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}"
|
||
. "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# add merge history to main ticket
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{MainTicketID},
|
||
HistoryType => 'Merged',
|
||
Name => "\%\%$MergeTicket{TicketNumber}\%\%$Param{MergeTicketID}"
|
||
. "\%\%$MainTicket{TicketNumber}\%\%$Param{MainTicketID}",
|
||
CreateUserID => $Param{UserID},
|
||
);
|
||
|
||
# transfer watchers - only those that were not already watching the main ticket
|
||
# delete all watchers from the merge ticket that are already watching the main ticket
|
||
my %MainWatchers = $Self->TicketWatchGet(
|
||
TicketID => $Param{MainTicketID},
|
||
);
|
||
|
||
my %MergeWatchers = $Self->TicketWatchGet(
|
||
TicketID => $Param{MergeTicketID},
|
||
);
|
||
|
||
WATCHER:
|
||
for my $WatcherID ( sort keys %MergeWatchers ) {
|
||
|
||
next WATCHER if !$MainWatchers{$WatcherID};
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
DELETE FROM ticket_watcher
|
||
WHERE user_id = ?
|
||
AND ticket_id = ?
|
||
',
|
||
Bind => [ \$WatcherID, \$Param{MergeTicketID} ],
|
||
);
|
||
}
|
||
|
||
# transfer remaining watchers to new ticket
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
UPDATE ticket_watcher
|
||
SET ticket_id = ?
|
||
WHERE ticket_id = ?
|
||
',
|
||
Bind => [ \$Param{MainTicketID}, \$Param{MergeTicketID} ],
|
||
);
|
||
|
||
# transfer all linked objects to new ticket
|
||
$Self->TicketMergeLinkedObjects(
|
||
MergeTicketID => $Param{MergeTicketID},
|
||
MainTicketID => $Param{MainTicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# link tickets
|
||
$Kernel::OM->Get('Kernel::System::LinkObject')->LinkAdd(
|
||
SourceObject => 'Ticket',
|
||
SourceKey => $Param{MainTicketID},
|
||
TargetObject => 'Ticket',
|
||
TargetKey => $Param{MergeTicketID},
|
||
Type => 'ParentChild',
|
||
State => 'Valid',
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# Update change time and user ID for main ticket.
|
||
# See bug#13092 for more information.
|
||
return if !$DBObject->Do(
|
||
SQL => 'UPDATE ticket SET change_time = current_timestamp, change_by = ? WHERE id = ?',
|
||
Bind => [ \$Param{UserID}, \$Param{MainTicketID} ],
|
||
);
|
||
|
||
# set new state of merge ticket
|
||
$Self->TicketStateSet(
|
||
State => $MergeStateList[0],
|
||
TicketID => $Param{MergeTicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# unlock ticket
|
||
$Self->LockSet(
|
||
Lock => 'unlock',
|
||
TicketID => $Param{MergeTicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# remove seen flag for all users on the main ticket
|
||
$Self->TicketFlagDelete(
|
||
TicketID => $Param{MainTicketID},
|
||
Key => 'Seen',
|
||
AllUsers => 1,
|
||
);
|
||
|
||
$Self->TicketMergeDynamicFields(
|
||
MergeTicketID => $Param{MergeTicketID},
|
||
MainTicketID => $Param{MainTicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
$Self->_TicketCacheClear( TicketID => $Param{MergeTicketID} );
|
||
$Self->_TicketCacheClear( TicketID => $Param{MainTicketID} );
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketMerge',
|
||
Data => {
|
||
TicketID => $Param{MergeTicketID},
|
||
MainTicketID => $Param{MainTicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketMergeDynamicFields()
|
||
|
||
merge dynamic fields from one ticket into another, that is, copy
|
||
them from the merge ticket to the main ticket if the value is empty
|
||
in the main ticket.
|
||
|
||
my $Success = $TicketObject->TicketMergeDynamicFields(
|
||
MainTicketID => 123,
|
||
MergeTicketID => 42,
|
||
UserID => 1,
|
||
DynamicFields => ['DynamicField_TicketFreeText1'], # optional
|
||
);
|
||
|
||
If DynamicFields is not present, it is taken from the Ticket::MergeDynamicFields
|
||
configuration.
|
||
|
||
=cut
|
||
|
||
sub TicketMergeDynamicFields {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
my $DynamicFields = $Param{DynamicFields};
|
||
|
||
if ( !$DynamicFields ) {
|
||
$DynamicFields = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::MergeDynamicFields');
|
||
}
|
||
|
||
return 1 if !IsArrayRefWithData($DynamicFields);
|
||
|
||
my %MainTicket = $Self->TicketGet(
|
||
TicketID => $Param{MainTicketID},
|
||
UserID => $Param{UserID},
|
||
DynamicFields => 1,
|
||
);
|
||
my %MergeTicket = $Self->TicketGet(
|
||
TicketID => $Param{MergeTicketID},
|
||
UserID => $Param{UserID},
|
||
DynamicFields => 1,
|
||
);
|
||
|
||
# get dynamic field objects
|
||
my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField');
|
||
my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend');
|
||
|
||
FIELDS:
|
||
for my $DynamicFieldName ( @{$DynamicFields} ) {
|
||
|
||
my $Key = "DynamicField_$DynamicFieldName";
|
||
|
||
if (
|
||
defined $MergeTicket{$Key}
|
||
&& length $MergeTicket{$Key}
|
||
&& !( defined $MainTicket{$Key} && length $MainTicket{$Key} )
|
||
)
|
||
{
|
||
|
||
my $DynamicFieldConfig = $DynamicFieldObject->DynamicFieldGet(
|
||
Name => $DynamicFieldName,
|
||
);
|
||
|
||
if ( !$DynamicFieldConfig ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'Error',
|
||
Message => qq[No such dynamic field "$DynamicFieldName"],
|
||
);
|
||
return;
|
||
}
|
||
|
||
$DynamicFieldBackendObject->ValueSet(
|
||
DynamicFieldConfig => $DynamicFieldConfig,
|
||
ObjectID => $Param{MainTicketID},
|
||
UserID => $Param{UserID},
|
||
Value => $MergeTicket{$Key},
|
||
);
|
||
}
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketMergeLinkedObjects()
|
||
|
||
merge linked objects from one ticket into another, that is, move
|
||
them from the merge ticket to the main ticket in the link_relation table.
|
||
|
||
my $Success = $TicketObject->TicketMergeLinkedObjects(
|
||
MainTicketID => 123,
|
||
MergeTicketID => 42,
|
||
UserID => 1,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketMergeLinkedObjects {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
for my $Needed (qw(MainTicketID MergeTicketID UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# Lookup the object id of a ticket.
|
||
my $TicketObjectID = $Kernel::OM->Get('Kernel::System::LinkObject')->ObjectLookup(
|
||
Name => 'Ticket',
|
||
);
|
||
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# Delete all duplicate links relations between merged tickets.
|
||
# See bug#12994 (https://bugs.otrs.org/show_bug.cgi?id=12994).
|
||
$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT target_key
|
||
FROM link_relation
|
||
WHERE target_object_id = ?
|
||
AND source_object_id = ?
|
||
AND source_key= ?
|
||
AND target_key
|
||
IN (SELECT target_key FROM link_relation WHERE source_key= ? )',
|
||
Bind => [
|
||
\$TicketObjectID,
|
||
\$TicketObjectID,
|
||
\$Param{MainTicketID},
|
||
\$Param{MergeTicketID},
|
||
],
|
||
);
|
||
|
||
my @Relations;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
push @Relations, $Row[0];
|
||
}
|
||
if (@Relations) {
|
||
|
||
my $SQL = "DELETE FROM link_relation
|
||
WHERE target_object_id = ?
|
||
AND source_object_id = ?
|
||
AND source_key = ?
|
||
AND target_key IN ( '${\(join '\',\'', @Relations)}' )";
|
||
|
||
$DBObject->Prepare(
|
||
SQL => $SQL,
|
||
Bind => [
|
||
\$TicketObjectID,
|
||
\$TicketObjectID,
|
||
\$Param{MergeTicketID},
|
||
],
|
||
);
|
||
}
|
||
|
||
# Update links from old ticket to new ticket where the old ticket is the source MainTicketID.
|
||
$DBObject->Do(
|
||
SQL => '
|
||
UPDATE link_relation
|
||
SET source_key = ?
|
||
WHERE source_object_id = ?
|
||
AND source_key = ?',
|
||
Bind => [
|
||
|
||
\$Param{MainTicketID},
|
||
\$TicketObjectID,
|
||
\$Param{MergeTicketID},
|
||
],
|
||
);
|
||
|
||
# Update links from old ticket to new ticket where the old ticket is the target.
|
||
$DBObject->Do(
|
||
SQL => '
|
||
UPDATE link_relation
|
||
SET target_key = ?
|
||
WHERE target_object_id = ?
|
||
AND target_key = ?',
|
||
Bind => [
|
||
\$Param{MainTicketID},
|
||
\$TicketObjectID,
|
||
\$Param{MergeTicketID},
|
||
],
|
||
);
|
||
|
||
# Delete all links between tickets where source and target object are the same.
|
||
$DBObject->Do(
|
||
SQL => '
|
||
DELETE FROM link_relation
|
||
WHERE source_object_id = ?
|
||
AND target_object_id = ?
|
||
AND source_key = target_key
|
||
',
|
||
Bind => [
|
||
\$TicketObjectID,
|
||
\$TicketObjectID,
|
||
],
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketWatchGet()
|
||
|
||
to get all user ids and additional attributes of an watched ticket
|
||
|
||
my %Watch = $TicketObject->TicketWatchGet(
|
||
TicketID => 123,
|
||
);
|
||
|
||
get list of users to notify
|
||
|
||
my %Watch = $TicketObject->TicketWatchGet(
|
||
TicketID => 123,
|
||
Notify => 1,
|
||
);
|
||
|
||
get list of users as array
|
||
|
||
my @Watch = $TicketObject->TicketWatchGet(
|
||
TicketID => 123,
|
||
Result => 'ARRAY',
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketWatchGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketID!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if feature is enabled
|
||
return if !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::Watcher');
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# get all attributes of an watched ticket
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT user_id, create_time, create_by, change_time, change_by
|
||
FROM ticket_watcher
|
||
WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# fetch the result
|
||
my %Data;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Data{ $Row[0] } = {
|
||
CreateTime => $Row[1],
|
||
CreateBy => $Row[2],
|
||
ChangeTime => $Row[3],
|
||
ChangeBy => $Row[4],
|
||
};
|
||
}
|
||
|
||
if ( $Param{Notify} ) {
|
||
|
||
for my $UserID ( sort keys %Data ) {
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
my %UserData = $UserObject->GetUserData(
|
||
UserID => $UserID,
|
||
Valid => 1,
|
||
);
|
||
|
||
if ( !$UserData{UserSendWatcherNotification} ) {
|
||
delete $Data{$UserID};
|
||
}
|
||
}
|
||
}
|
||
|
||
# check result
|
||
if ( $Param{Result} && $Param{Result} eq 'ARRAY' ) {
|
||
|
||
my @UserIDs;
|
||
|
||
for my $UserID ( sort keys %Data ) {
|
||
push @UserIDs, $UserID;
|
||
}
|
||
|
||
return @UserIDs;
|
||
}
|
||
|
||
return %Data;
|
||
}
|
||
|
||
=head2 TicketWatchSubscribe()
|
||
|
||
to subscribe a ticket to watch it
|
||
|
||
my $Success = $TicketObject->TicketWatchSubscribe(
|
||
TicketID => 111,
|
||
WatchUserID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketSubscribe
|
||
|
||
=cut
|
||
|
||
sub TicketWatchSubscribe {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID WatchUserID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# db access
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
DELETE FROM ticket_watcher
|
||
WHERE ticket_id = ?
|
||
AND user_id = ?',
|
||
Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ],
|
||
);
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
INSERT INTO ticket_watcher (ticket_id, user_id, create_time, create_by, change_time, change_by)
|
||
VALUES (?, ?, current_timestamp, ?, current_timestamp, ?)',
|
||
Bind => [ \$Param{TicketID}, \$Param{WatchUserID}, \$Param{UserID}, \$Param{UserID} ],
|
||
);
|
||
|
||
# get user data
|
||
my %User = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
||
UserID => $Param{WatchUserID},
|
||
);
|
||
|
||
# add history
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'Subscribe',
|
||
Name => "\%\%$User{UserFullname}",
|
||
);
|
||
|
||
# trigger event
|
||
$Self->EventHandler(
|
||
Event => 'TicketSubscribe',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketWatchUnsubscribe()
|
||
|
||
to remove a subscription of a ticket
|
||
|
||
my $Success = $TicketObject->TicketWatchUnsubscribe(
|
||
TicketID => 111,
|
||
WatchUserID => 123,
|
||
UserID => 123,
|
||
);
|
||
|
||
Events:
|
||
TicketUnsubscribe
|
||
|
||
=cut
|
||
|
||
sub TicketWatchUnsubscribe {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# only one of these parameters is needed
|
||
if ( !$Param{WatchUserID} && !$Param{AllUsers} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need WatchUserID or AllUsers param!"
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get user object
|
||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||
|
||
if ( $Param{AllUsers} ) {
|
||
my @WatchUsers = $Self->TicketWatchGet(
|
||
TicketID => $Param{TicketID},
|
||
Result => 'ARRAY',
|
||
);
|
||
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
for my $WatchUser (@WatchUsers) {
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $WatchUser,
|
||
);
|
||
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'Unsubscribe',
|
||
Name => "\%\%$User{UserFullname}",
|
||
);
|
||
|
||
$Self->EventHandler(
|
||
Event => 'TicketUnsubscribe',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
}
|
||
else {
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => 'DELETE FROM ticket_watcher WHERE ticket_id = ? AND user_id = ?',
|
||
Bind => [ \$Param{TicketID}, \$Param{WatchUserID} ],
|
||
);
|
||
|
||
my %User = $UserObject->GetUserData(
|
||
UserID => $Param{WatchUserID},
|
||
);
|
||
|
||
$Self->HistoryAdd(
|
||
TicketID => $Param{TicketID},
|
||
CreateUserID => $Param{UserID},
|
||
HistoryType => 'Unsubscribe',
|
||
Name => "\%\%$User{UserFullname}",
|
||
);
|
||
|
||
$Self->EventHandler(
|
||
Event => 'TicketUnsubscribe',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketFlagSet()
|
||
|
||
set ticket flags
|
||
|
||
my $Success = $TicketObject->TicketFlagSet(
|
||
TicketID => 123,
|
||
Key => 'Seen',
|
||
Value => 1,
|
||
UserID => 123, # apply to this user
|
||
);
|
||
|
||
Events:
|
||
TicketFlagSet
|
||
|
||
=cut
|
||
|
||
sub TicketFlagSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Key Value UserID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get flags
|
||
my %Flag = $Self->TicketFlagGet(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
# check if set is needed
|
||
return 1 if defined $Flag{ $Param{Key} } && $Flag{ $Param{Key} } eq $Param{Value};
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# set flag
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
DELETE FROM ticket_flag
|
||
WHERE ticket_id = ?
|
||
AND ticket_key = ?
|
||
AND create_by = ?',
|
||
Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{UserID} ],
|
||
);
|
||
return if !$DBObject->Do(
|
||
SQL => '
|
||
INSERT INTO ticket_flag
|
||
(ticket_id, ticket_key, ticket_value, create_time, create_by)
|
||
VALUES (?, ?, ?, current_timestamp, ?)',
|
||
Bind => [ \$Param{TicketID}, \$Param{Key}, \$Param{Value}, \$Param{UserID} ],
|
||
);
|
||
|
||
# delete cache
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
);
|
||
|
||
# event
|
||
$Self->EventHandler(
|
||
Event => 'TicketFlagSet',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
Key => $Param{Key},
|
||
Value => $Param{Value},
|
||
UserID => $Param{UserID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketFlagDelete()
|
||
|
||
delete ticket flag
|
||
|
||
my $Success = $TicketObject->TicketFlagDelete(
|
||
TicketID => 123,
|
||
Key => 'Seen',
|
||
UserID => 123,
|
||
);
|
||
|
||
my $Success = $TicketObject->TicketFlagDelete(
|
||
TicketID => 123,
|
||
Key => 'Seen',
|
||
AllUsers => 1,
|
||
);
|
||
|
||
Events:
|
||
TicketFlagDelete
|
||
|
||
=cut
|
||
|
||
sub TicketFlagDelete {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Key)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# only one of these parameters is needed
|
||
if ( !$Param{UserID} && !$Param{AllUsers} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need UserID or AllUsers param!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# if all users parameter was given
|
||
if ( $Param{AllUsers} ) {
|
||
|
||
# get all affected users
|
||
my @AllTicketFlags = $Self->TicketFlagGet(
|
||
TicketID => $Param{TicketID},
|
||
AllUsers => 1,
|
||
);
|
||
|
||
# delete flags from database
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => '
|
||
DELETE FROM ticket_flag
|
||
WHERE ticket_id = ?
|
||
AND ticket_key = ?',
|
||
Bind => [ \$Param{TicketID}, \$Param{Key} ],
|
||
);
|
||
|
||
# delete cache
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
);
|
||
|
||
for my $Record (@AllTicketFlags) {
|
||
|
||
$Self->EventHandler(
|
||
Event => 'TicketFlagDelete',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
Key => $Param{Key},
|
||
UserID => $Record->{UserID},
|
||
},
|
||
UserID => $Record->{UserID},
|
||
);
|
||
}
|
||
}
|
||
else {
|
||
|
||
# delete flags from database
|
||
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||
SQL => '
|
||
DELETE FROM ticket_flag
|
||
WHERE ticket_id = ?
|
||
AND create_by = ?
|
||
AND ticket_key = ?',
|
||
Bind => [ \$Param{TicketID}, \$Param{UserID}, \$Param{Key} ],
|
||
);
|
||
|
||
# delete cache
|
||
$Kernel::OM->Get('Kernel::System::Cache')->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
);
|
||
|
||
$Self->EventHandler(
|
||
Event => 'TicketFlagDelete',
|
||
Data => {
|
||
TicketID => $Param{TicketID},
|
||
Key => $Param{Key},
|
||
UserID => $Param{UserID},
|
||
},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 TicketFlagGet()
|
||
|
||
get ticket flags
|
||
|
||
my %Flags = $TicketObject->TicketFlagGet(
|
||
TicketID => 123,
|
||
UserID => 123, # to get flags of one user
|
||
);
|
||
|
||
my @Flags = $TicketObject->TicketFlagGet(
|
||
TicketID => 123,
|
||
AllUsers => 1, # to get flags of all users
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketFlagGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need TicketID!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check optional
|
||
if ( !$Param{UserID} && !$Param{AllUsers} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need UserID or AllUsers param!",
|
||
);
|
||
return;
|
||
}
|
||
|
||
# get cache object
|
||
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
|
||
|
||
# check cache
|
||
my $Flags = $CacheObject->Get(
|
||
Type => $Self->{CacheType},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
);
|
||
|
||
if ( !$Flags || ref $Flags ne 'HASH' ) {
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# get all ticket flags of the given ticket
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT create_by, ticket_key, ticket_value
|
||
FROM ticket_flag
|
||
WHERE ticket_id = ?',
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
# fetch the result
|
||
$Flags = {};
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Flags->{ $Row[0] }->{ $Row[1] } = $Row[2];
|
||
}
|
||
|
||
# set cache
|
||
$CacheObject->Set(
|
||
Type => $Self->{CacheType},
|
||
TTL => $Self->{CacheTTL},
|
||
Key => 'TicketFlag::' . $Param{TicketID},
|
||
Value => $Flags,
|
||
);
|
||
}
|
||
|
||
if ( $Param{AllUsers} ) {
|
||
|
||
my @FlagAllUsers;
|
||
for my $UserID ( sort keys %{$Flags} ) {
|
||
|
||
for my $Key ( sort keys %{ $Flags->{$UserID} } ) {
|
||
|
||
push @FlagAllUsers, {
|
||
Key => $Key,
|
||
Value => $Flags->{$UserID}->{$Key},
|
||
UserID => $UserID,
|
||
};
|
||
}
|
||
}
|
||
|
||
return @FlagAllUsers;
|
||
}
|
||
|
||
# extract user tags
|
||
my $UserTags = $Flags->{ $Param{UserID} } || {};
|
||
|
||
return %{$UserTags};
|
||
}
|
||
|
||
=head2 TicketArticleStorageSwitch()
|
||
|
||
move article storage from one backend to other backend
|
||
|
||
my $Success = $TicketObject->TicketArticleStorageSwitch(
|
||
TicketID => 123,
|
||
Source => 'ArticleStorageDB',
|
||
Destination => 'ArticleStorageFS',
|
||
UserID => 1,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketArticleStorageSwitch {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Source Destination UserID)) {
|
||
if ( !$Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# check source vs. destination
|
||
return 1 if $Param{Source} eq $Param{Destination};
|
||
|
||
# get config object
|
||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||
|
||
# reset events and remember
|
||
my $EventConfig = $ConfigObject->Get('Ticket::EventModulePost');
|
||
$ConfigObject->{'Ticket::EventModulePost'} = {};
|
||
|
||
# make sure that CheckAllBackends is set for the duration of this method
|
||
$Self->{CheckAllBackends} = 1;
|
||
|
||
my $MainObject = $Kernel::OM->Get('Kernel::System::Main');
|
||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||
|
||
# Get articles.
|
||
my @Articles = $ArticleObject->ArticleList(
|
||
TicketID => $Param{TicketID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
|
||
ARTICLE:
|
||
for my $Article (@Articles) {
|
||
|
||
# Handle only MIME based articles.
|
||
my $BackendName = $ArticleObject->BackendForArticle(
|
||
TicketID => $Param{TicketID},
|
||
ArticleID => $Article->{ArticleID}
|
||
)->ChannelNameGet();
|
||
next ARTICLE if $BackendName !~ /^(Email|Phone|Internal)$/;
|
||
|
||
my $ArticleObjectSource = Kernel::System::Ticket::Article::Backend::MIMEBase->new(
|
||
ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Source},
|
||
);
|
||
if (
|
||
!$ArticleObjectSource
|
||
|| $ArticleObjectSource->{ArticleStorageModule} ne
|
||
"Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Source}"
|
||
)
|
||
{
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Could not create Kernel::System::Ticket::Article::Backend::MIMEBase::" . $Param{Source},
|
||
);
|
||
die;
|
||
}
|
||
|
||
# read source attachments
|
||
my %Index = $ArticleObjectSource->ArticleAttachmentIndex(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# read source plain
|
||
my $Plain = $ArticleObjectSource->ArticlePlain(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
my $PlainMD5Sum = '';
|
||
if ($Plain) {
|
||
my $PlainMD5 = $Plain;
|
||
$PlainMD5Sum = $MainObject->MD5sum(
|
||
String => \$PlainMD5,
|
||
);
|
||
}
|
||
|
||
# read source attachments
|
||
my @Attachments;
|
||
my %MD5Sums;
|
||
for my $FileID ( sort keys %Index ) {
|
||
my %Attachment = $ArticleObjectSource->ArticleAttachment(
|
||
ArticleID => $Article->{ArticleID},
|
||
FileID => $FileID,
|
||
OnlyMyBackend => 1,
|
||
Force => 1,
|
||
);
|
||
push @Attachments, \%Attachment;
|
||
my $MD5Sum = $MainObject->MD5sum(
|
||
String => $Attachment{Content},
|
||
);
|
||
$MD5Sums{$MD5Sum}++;
|
||
}
|
||
|
||
# nothing to transfer
|
||
next ARTICLE if !@Attachments && !$Plain;
|
||
|
||
my $ArticleObjectDestination = Kernel::System::Ticket::Article::Backend::MIMEBase->new(
|
||
ArticleStorageModule => 'Kernel::System::Ticket::Article::Backend::MIMEBase::' . $Param{Destination},
|
||
);
|
||
if (
|
||
!$ArticleObjectDestination
|
||
|| $ArticleObjectDestination->{ArticleStorageModule} ne
|
||
"Kernel::System::Ticket::Article::Backend::MIMEBase::$Param{Destination}"
|
||
)
|
||
{
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Could not create Kernel::System::Ticket::" . $Param{Destination},
|
||
);
|
||
die;
|
||
}
|
||
|
||
# read destination attachments
|
||
%Index = $ArticleObjectDestination->ArticleAttachmentIndex(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# read source attachments
|
||
if (%Index) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message =>
|
||
"Attachments of TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID} already in $Param{Destination}!",
|
||
);
|
||
}
|
||
else {
|
||
|
||
# write attachments to destination
|
||
for my $Attachment (@Attachments) {
|
||
|
||
# Check UTF8 string for validity and replace any wrongly encoded characters with _
|
||
if (
|
||
utf8::is_utf8( $Attachment->{Filename} )
|
||
&& !eval { Encode::is_utf8( $Attachment->{Filename}, 1 ) }
|
||
)
|
||
{
|
||
|
||
Encode::_utf8_off( $Attachment->{Filename} );
|
||
|
||
# replace invalid characters with <20> (U+FFFD, Unicode replacement character)
|
||
# If it runs on good UTF-8 input, output should be identical to input
|
||
$Attachment->{Filename} = eval {
|
||
Encode::decode( 'UTF-8', $Attachment->{Filename} );
|
||
};
|
||
|
||
# Replace wrong characters with "_".
|
||
$Attachment->{Filename} =~ s{[\x{FFFD}]}{_}xms;
|
||
}
|
||
|
||
$ArticleObjectDestination->ArticleWriteAttachment(
|
||
%{$Attachment},
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# write destination plain
|
||
if ($Plain) {
|
||
$ArticleObjectDestination->ArticleWritePlain(
|
||
Email => $Plain,
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => $Param{UserID},
|
||
);
|
||
}
|
||
|
||
# verify destination attachments
|
||
%Index = $ArticleObjectDestination->ArticleAttachmentIndex(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
}
|
||
|
||
for my $FileID ( sort keys %Index ) {
|
||
my %Attachment = $ArticleObjectDestination->ArticleAttachment(
|
||
ArticleID => $Article->{ArticleID},
|
||
FileID => $FileID,
|
||
OnlyMyBackend => 1,
|
||
Force => 1,
|
||
);
|
||
my $MD5Sum = $MainObject->MD5sum(
|
||
String => \$Attachment{Content},
|
||
);
|
||
if ( $MD5Sums{$MD5Sum} ) {
|
||
$MD5Sums{$MD5Sum}--;
|
||
if ( !$MD5Sums{$MD5Sum} ) {
|
||
delete $MD5Sums{$MD5Sum};
|
||
}
|
||
}
|
||
else {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message =>
|
||
"Corrupt file: $Attachment{Filename} (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!",
|
||
);
|
||
|
||
# delete corrupt attachments from destination
|
||
$ArticleObjectDestination->ArticleDeleteAttachment(
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => 1,
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# set events
|
||
$ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
|
||
return;
|
||
}
|
||
}
|
||
|
||
# check if all files are moved
|
||
if (%MD5Sums) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message =>
|
||
"Not all files are moved! (TicketID:$Param{TicketID}/ArticleID:$Article->{ArticleID})!",
|
||
);
|
||
|
||
# delete incomplete attachments from destination
|
||
$ArticleObjectDestination->ArticleDeleteAttachment(
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => 1,
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# set events
|
||
$ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
|
||
return;
|
||
}
|
||
|
||
# verify destination plain if exists in source backend
|
||
if ($Plain) {
|
||
my $PlainVerify = $ArticleObjectDestination->ArticlePlain(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
my $PlainMD5SumVerify = '';
|
||
if ($PlainVerify) {
|
||
$PlainMD5SumVerify = $MainObject->MD5sum(
|
||
String => \$PlainVerify,
|
||
);
|
||
}
|
||
if ( $PlainMD5Sum ne $PlainMD5SumVerify ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message =>
|
||
"Corrupt plain file: ArticleID: $Article->{ArticleID} ($PlainMD5Sum/$PlainMD5SumVerify)",
|
||
);
|
||
|
||
# delete corrupt plain file from destination
|
||
$ArticleObjectDestination->ArticleDeletePlain(
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => 1,
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# set events
|
||
$ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
|
||
return;
|
||
}
|
||
}
|
||
|
||
$ArticleObjectSource->ArticleDeleteAttachment(
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => 1,
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# remove source plain
|
||
$ArticleObjectSource->ArticleDeletePlain(
|
||
ArticleID => $Article->{ArticleID},
|
||
UserID => 1,
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# read source attachments
|
||
%Index = $ArticleObjectSource->ArticleAttachmentIndex(
|
||
ArticleID => $Article->{ArticleID},
|
||
OnlyMyBackend => 1,
|
||
);
|
||
|
||
# read source attachments
|
||
if (%Index) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Attachments still in $Param{Source}!",
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# set events
|
||
$ConfigObject->{'Ticket::EventModulePost'} = $EventConfig;
|
||
|
||
# Restore previous behavior.
|
||
$Self->{CheckAllBackends} =
|
||
$ConfigObject->Get('Ticket::Article::Backend::MIMEBase::CheckAllStorageBackends')
|
||
// 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
# ProcessManagement functions
|
||
|
||
=head2 TicketCheckForProcessType()
|
||
|
||
checks whether or not the ticket is of a process type.
|
||
|
||
$TicketObject->TicketCheckForProcessType(
|
||
TicketID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub TicketCheckForProcessType {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{TicketID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need TicketID!',
|
||
);
|
||
return;
|
||
}
|
||
|
||
my $DynamicFieldName = $Kernel::OM->Get('Kernel::Config')->Get('Process::DynamicFieldProcessManagementProcessID');
|
||
|
||
return if !$DynamicFieldName;
|
||
$DynamicFieldName = 'DynamicField_' . $DynamicFieldName;
|
||
|
||
# get ticket attributes
|
||
my %Ticket = $Self->TicketGet(
|
||
TicketID => $Param{TicketID},
|
||
DynamicFields => 1,
|
||
);
|
||
|
||
# return 1 if we got process ticket
|
||
return 1 if $Ticket{$DynamicFieldName};
|
||
|
||
return;
|
||
}
|
||
|
||
=head2 TicketCalendarGet()
|
||
|
||
checks calendar to be used for ticket based on sla and queue
|
||
|
||
my $Calendar = $TicketObject->TicketCalendarGet(
|
||
QueueID => 1,
|
||
SLAID => 1, # optional
|
||
);
|
||
|
||
returns calendar number or empty string for default calendar
|
||
|
||
=cut
|
||
|
||
sub TicketCalendarGet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
if ( !$Param{QueueID} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => 'Need QueueID!'
|
||
);
|
||
return;
|
||
}
|
||
|
||
# check if SLAID was passed and if sla has a specific calendar
|
||
if ( $Param{SLAID} ) {
|
||
|
||
my %SLAData = $Kernel::OM->Get('Kernel::System::SLA')->SLAGet(
|
||
SLAID => $Param{SLAID},
|
||
UserID => 1,
|
||
);
|
||
|
||
# if SLA has a defined calendar, return it
|
||
return $SLAData{Calendar} if $SLAData{Calendar};
|
||
}
|
||
|
||
# if no calendar was determined by SLA, check if queue has a specific calendar
|
||
my %QueueData = $Kernel::OM->Get('Kernel::System::Queue')->QueueGet(
|
||
ID => $Param{QueueID},
|
||
);
|
||
|
||
# if queue has a defined calendar, return it
|
||
return $QueueData{Calendar} if $QueueData{Calendar};
|
||
|
||
# use default calendar
|
||
return '';
|
||
}
|
||
|
||
=head2 SearchUnknownTicketCustomers()
|
||
|
||
search customer users that are not saved in any backend
|
||
|
||
my $UnknownTicketCustomerList = $TicketObject->SearchUnknownTicketCustomers(
|
||
SearchTerm => 'SomeSearchTerm',
|
||
);
|
||
|
||
Returns:
|
||
|
||
%UnknownTicketCustomerList = (
|
||
{
|
||
CustomerID => 'SomeCustomerID',
|
||
CustomerUser => 'SomeCustomerUser',
|
||
},
|
||
{
|
||
CustomerID => 'SomeCustomerID',
|
||
CustomerUser => 'SomeCustomerUser',
|
||
},
|
||
);
|
||
|
||
=cut
|
||
|
||
sub SearchUnknownTicketCustomers {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $SearchTerm = $Param{SearchTerm} || '';
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
my $LikeEscapeString = $DBObject->GetDatabaseFunction('LikeEscapeString');
|
||
my $QuotedSearch = '%' . $DBObject->Quote( $SearchTerm, 'Like' ) . '%';
|
||
|
||
# db query
|
||
return if !$DBObject->Prepare(
|
||
SQL =>
|
||
"SELECT DISTINCT customer_user_id, customer_id FROM ticket WHERE customer_user_id LIKE ? $LikeEscapeString",
|
||
Bind => [ \$QuotedSearch ],
|
||
);
|
||
my $UnknownTicketCustomerList;
|
||
|
||
CUSTOMERUSER:
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$UnknownTicketCustomerList->{ $Row[0] } = $Row[1];
|
||
}
|
||
|
||
return $UnknownTicketCustomerList;
|
||
}
|
||
|
||
sub TicketAcceleratorUpdate {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
|
||
|| 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
|
||
|
||
return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorUpdate(%Param);
|
||
}
|
||
|
||
sub TicketAcceleratorDelete {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
|
||
|| 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
|
||
|
||
return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorDelete(%Param);
|
||
}
|
||
|
||
sub TicketAcceleratorAdd {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
|
||
|| 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
|
||
|
||
return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorAdd(%Param);
|
||
}
|
||
|
||
sub TicketAcceleratorIndex {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
|
||
|| 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
|
||
|
||
return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorIndex(%Param);
|
||
}
|
||
|
||
sub TicketAcceleratorRebuild {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
my $TicketIndexModule = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule')
|
||
|| 'Kernel::System::Ticket::IndexAccelerator::RuntimeDB';
|
||
|
||
return $Kernel::OM->Get($TicketIndexModule)->TicketAcceleratorRebuild(%Param);
|
||
}
|
||
|
||
sub DESTROY {
|
||
my $Self = shift;
|
||
|
||
# execute all transaction events
|
||
$Self->EventHandlerTransaction();
|
||
|
||
return 1;
|
||
}
|
||
|
||
# COMPAT: to OTRS 1.x and 2.x (can be removed later)
|
||
|
||
sub CustomerPermission {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketCustomerPermission(%Param);
|
||
}
|
||
|
||
sub InvolvedAgents {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketInvolvedAgentsList(%Param);
|
||
}
|
||
|
||
sub LockIsTicketLocked {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketLockGet(%Param);
|
||
}
|
||
|
||
sub LockSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketLockSet(%Param);
|
||
}
|
||
|
||
sub MoveList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketMoveList(%Param);
|
||
}
|
||
|
||
sub MoveTicket {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketQueueSet(%Param);
|
||
}
|
||
|
||
sub MoveQueueList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketMoveQueueList(%Param);
|
||
}
|
||
|
||
sub OwnerList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketOwnerList(%Param);
|
||
}
|
||
|
||
sub OwnerSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketOwnerSet(%Param);
|
||
}
|
||
|
||
sub Permission {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketPermission(%Param);
|
||
}
|
||
|
||
sub PriorityList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketPriorityList(%Param);
|
||
}
|
||
|
||
sub PrioritySet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketPrioritySet(%Param);
|
||
}
|
||
|
||
sub ResponsibleList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketResponsibleList(%Param);
|
||
}
|
||
|
||
sub ResponsibleSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketResponsibleSet(%Param);
|
||
}
|
||
|
||
sub SetCustomerData {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketCustomerSet(%Param);
|
||
}
|
||
|
||
sub StateList {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketStateList(%Param);
|
||
}
|
||
|
||
sub StateSet {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
return $Self->TicketStateSet(%Param);
|
||
}
|
||
|
||
=head1 PRIVATE FUNCTIONS
|
||
|
||
=head2 _TicketCacheClear()
|
||
|
||
Remove all caches related to specified ticket.
|
||
|
||
my $Success = $TicketObject->_TicketCacheClear(
|
||
TicketID => 123,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub _TicketCacheClear {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
for my $Needed (qw(TicketID)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get cache object
|
||
my $CacheObject = $Kernel::OM->Get('Kernel::System::Cache');
|
||
|
||
# TicketGet()
|
||
my $CacheKey = 'Cache::GetTicket' . $Param{TicketID};
|
||
$CacheObject->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKey,
|
||
);
|
||
|
||
# delete extended cache for TicketGet()
|
||
for my $Extended ( 0 .. 1 ) {
|
||
for my $FetchDynamicFields ( 0 .. 1 ) {
|
||
my $CacheKeyDynamicFields = $CacheKey . '::' . $Extended . '::' . $FetchDynamicFields;
|
||
|
||
$CacheObject->Delete(
|
||
Type => $Self->{CacheType},
|
||
Key => $CacheKeyDynamicFields,
|
||
);
|
||
}
|
||
}
|
||
|
||
$Kernel::OM->Get('Kernel::System::Ticket::Article')->_ArticleCacheClear(%Param);
|
||
|
||
return 1;
|
||
}
|
||
|
||
=head2 _TicketGetExtended()
|
||
|
||
Collect extended attributes for given ticket,
|
||
namely first response, first lock and close data.
|
||
|
||
my %TicketExtended = $TicketObject->_TicketGetExtended(
|
||
TicketID => $Param{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub _TicketGetExtended {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Ticket)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get extended attributes
|
||
my %FirstResponse = $Self->_TicketGetFirstResponse(%Param);
|
||
my %FirstLock = $Self->_TicketGetFirstLock(%Param);
|
||
my %TicketGetClosed = $Self->_TicketGetClosed(%Param);
|
||
|
||
# return all as hash
|
||
return ( %TicketGetClosed, %FirstResponse, %FirstLock );
|
||
}
|
||
|
||
=head2 _TicketGetFirstResponse()
|
||
|
||
Collect attributes of first response for given ticket.
|
||
|
||
my %FirstResponse = $TicketObject->_TicketGetFirstResponse(
|
||
TicketID => $Param{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub _TicketGetFirstResponse {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Ticket)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# check if first response is already done
|
||
return if !$DBObject->Prepare(
|
||
SQL => '
|
||
SELECT a.create_time,a.id FROM article a, article_sender_type ast
|
||
WHERE a.article_sender_type_id = ast.id
|
||
AND a.ticket_id = ?
|
||
AND ast.name = ?
|
||
AND a.is_visible_for_customer = ?
|
||
ORDER BY a.create_time',
|
||
Bind => [ \$Param{TicketID}, \'agent', \1 ],
|
||
Limit => 1,
|
||
);
|
||
|
||
my %Data;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Data{FirstResponse} = $Row[0];
|
||
|
||
# cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000
|
||
# and 0000-00-00 00:00:00 time stamps)
|
||
$Data{FirstResponse} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/;
|
||
}
|
||
|
||
return if !$Data{FirstResponse};
|
||
|
||
# get escalation properties
|
||
my %Escalation = $Self->TicketEscalationPreferences(
|
||
Ticket => $Param{Ticket},
|
||
UserID => $Param{UserID} || 1,
|
||
);
|
||
|
||
if ( $Escalation{FirstResponseTime} ) {
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Param{Ticket}->{Created},
|
||
}
|
||
);
|
||
|
||
my $FirstResponseTimeObj = $DateTimeObject->Clone();
|
||
$FirstResponseTimeObj->Set(
|
||
String => $Data{FirstResponse}
|
||
);
|
||
|
||
my $DeltaObj = $DateTimeObject->Delta(
|
||
DateTimeObject => $FirstResponseTimeObj,
|
||
ForWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
|
||
|
||
$Data{FirstResponseInMin} = int( $WorkingTime / 60 );
|
||
my $EscalationFirstResponseTime = $Escalation{FirstResponseTime} * 60;
|
||
$Data{FirstResponseDiffInMin} =
|
||
int( ( $EscalationFirstResponseTime - $WorkingTime ) / 60 );
|
||
}
|
||
|
||
return %Data;
|
||
}
|
||
|
||
=head2 _TicketGetClosed()
|
||
|
||
Collect attributes of (last) closing for given ticket.
|
||
|
||
my %TicketGetClosed = $TicketObject->_TicketGetClosed(
|
||
TicketID => $Param{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub _TicketGetClosed {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Ticket)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
# get close state types
|
||
my @List = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
|
||
StateType => ['closed'],
|
||
Result => 'ID',
|
||
);
|
||
return if !@List;
|
||
|
||
# Get id for history types
|
||
my @HistoryTypeIDs;
|
||
for my $HistoryType (qw(StateUpdate NewTicket)) {
|
||
push @HistoryTypeIDs, $Self->HistoryTypeLookup( Type => $HistoryType );
|
||
}
|
||
|
||
# get database object
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
return if !$DBObject->Prepare(
|
||
SQL => "
|
||
SELECT MAX(create_time)
|
||
FROM ticket_history
|
||
WHERE ticket_id = ?
|
||
AND state_id IN (${\(join ', ', sort @List)})
|
||
AND history_type_id IN (${\(join ', ', sort @HistoryTypeIDs)})
|
||
",
|
||
Bind => [ \$Param{TicketID} ],
|
||
);
|
||
|
||
my %Data;
|
||
ROW:
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
last ROW if !defined $Row[0];
|
||
$Data{Closed} = $Row[0];
|
||
|
||
# cleanup time stamps (some databases are using e. g. 2008-02-25 22:03:00.000000
|
||
# and 0000-00-00 00:00:00 time stamps)
|
||
$Data{Closed} =~ s/^(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\..+?$/$1/;
|
||
}
|
||
|
||
return if !$Data{Closed};
|
||
|
||
# get escalation properties
|
||
my %Escalation = $Self->TicketEscalationPreferences(
|
||
Ticket => $Param{Ticket},
|
||
UserID => $Param{UserID} || 1,
|
||
);
|
||
|
||
# create datetime object
|
||
my $DateTimeObject = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Param{Ticket}->{Created},
|
||
}
|
||
);
|
||
|
||
my $SolutionTimeObj = $Kernel::OM->Create(
|
||
'Kernel::System::DateTime',
|
||
ObjectParams => {
|
||
String => $Data{Closed},
|
||
}
|
||
);
|
||
|
||
my $DeltaObj = $DateTimeObject->Delta(
|
||
DateTimeObject => $SolutionTimeObj,
|
||
ForWorkingTime => 1,
|
||
Calendar => $Escalation{Calendar},
|
||
);
|
||
|
||
my $WorkingTime = $DeltaObj ? $DeltaObj->{AbsoluteSeconds} : 0;
|
||
|
||
$Data{SolutionInMin} = int( $WorkingTime / 60 );
|
||
|
||
if ( $Escalation{SolutionTime} ) {
|
||
my $EscalationSolutionTime = $Escalation{SolutionTime} * 60;
|
||
$Data{SolutionDiffInMin} =
|
||
int( ( $EscalationSolutionTime - $WorkingTime ) / 60 );
|
||
}
|
||
|
||
return %Data;
|
||
}
|
||
|
||
=head2 _TicketGetFirstLock()
|
||
|
||
Collect first lock time for given ticket.
|
||
|
||
my %FirstLock = $TicketObject->_TicketGetFirstLock(
|
||
TicketID => $Param{TicketID},
|
||
Ticket => \%Ticket,
|
||
);
|
||
|
||
=cut
|
||
|
||
sub _TicketGetFirstLock {
|
||
my ( $Self, %Param ) = @_;
|
||
|
||
# check needed stuff
|
||
for my $Needed (qw(TicketID Ticket)) {
|
||
if ( !defined $Param{$Needed} ) {
|
||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||
Priority => 'error',
|
||
Message => "Need $Needed!"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
my $LockHistoryTypeID = $Self->HistoryTypeLookup( Type => 'Lock' );
|
||
|
||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||
|
||
# get first lock
|
||
return if !$DBObject->Prepare(
|
||
SQL => 'SELECT create_time '
|
||
. 'FROM ticket_history '
|
||
. 'WHERE ticket_id = ? AND history_type_id = ? '
|
||
. 'ORDER BY create_time ASC, id ASC',
|
||
Bind => [ \$Param{TicketID}, \$LockHistoryTypeID, ],
|
||
Limit => 1,
|
||
);
|
||
|
||
my %Data;
|
||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||
$Data{FirstLock} = $Row[0];
|
||
|
||
# cleanup time stamp (some databases are using e. g. '2008-02-25 22:03:00.000000' time stamps)
|
||
$Data{FirstLock} =~ s{ \A ( \d{4} - \d{2} - \d{2} [ ] \d{2} : \d{2} : \d{2} ) .* \z }{$1}xms;
|
||
}
|
||
|
||
return %Data;
|
||
}
|
||
|
||
1;
|
||
|
||
=head1 TERMS AND CONDITIONS
|
||
|
||
This software is part of the OTRS project (L<https://otrs.org/>).
|
||
|
||
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 L<https://www.gnu.org/licenses/gpl-3.0.txt>.
|
||
|
||
=cut
|