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 � (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
|