init III
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::ArchiveCleanup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Time::HiRes();
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DB',
|
||||
'Kernel::System::Ticket',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Delete ticket/article seen flags and ticket watcher entries for archived tickets.');
|
||||
$Self->AddOption(
|
||||
Name => 'micro-sleep',
|
||||
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub PreRun {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
if ( !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) {
|
||||
die "Ticket::ArchiveSystem is not enabled!\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Cleaning up ticket archive...</yellow>\n");
|
||||
|
||||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
my $MicroSleep = $Self->GetOption('micro-sleep');
|
||||
|
||||
if ( $ConfigObject->Get('Ticket::ArchiveSystem::RemoveSeenFlags') ) {
|
||||
|
||||
$Self->Print("<yellow>Checking for archived tickets with seen flags...</yellow>\n");
|
||||
|
||||
# Find all archived tickets which have ticket seen flags set
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(ticket.id)
|
||||
FROM ticket
|
||||
INNER JOIN ticket_flag ON ticket.id = ticket_flag.ticket_id
|
||||
WHERE ticket.archive_flag = 1
|
||||
AND ticket_flag.ticket_key = 'Seen'",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
my @TicketIDs;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @TicketIDs, $Row[0];
|
||||
}
|
||||
|
||||
my $Count = 0;
|
||||
for my $TicketID (@TicketIDs) {
|
||||
$TicketObject->TicketFlagDelete(
|
||||
TicketID => $TicketID,
|
||||
Key => 'Seen',
|
||||
AllUsers => 1,
|
||||
);
|
||||
$Count++;
|
||||
$Self->Print(" Removing seen flags of ticket $TicketID\n");
|
||||
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> tickets).\n");
|
||||
$Self->Print("<yellow>Checking for archived articles with seen flags...</yellow>\n");
|
||||
|
||||
# Find all articles of archived tickets which have ticket seen flags set
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(article.id)
|
||||
FROM article
|
||||
INNER JOIN ticket ON ticket.id = article.ticket_id
|
||||
INNER JOIN article_flag ON article.id = article_flag.article_id
|
||||
WHERE ticket.archive_flag = 1
|
||||
AND article_flag.article_key = 'Seen'",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
my @ArticleIDs;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @ArticleIDs, $Row[0];
|
||||
}
|
||||
|
||||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
|
||||
$Count = 0;
|
||||
for my $ArticleID (@ArticleIDs) {
|
||||
$ArticleObject->ArticleFlagDelete(
|
||||
ArticleID => $ArticleID,
|
||||
Key => 'Seen',
|
||||
AllUsers => 1,
|
||||
);
|
||||
$Count++;
|
||||
$Self->Print(" Removing seen flags of article $ArticleID\n");
|
||||
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> articles).\n");
|
||||
}
|
||||
|
||||
if (
|
||||
$ConfigObject->Get('Ticket::ArchiveSystem::RemoveTicketWatchers')
|
||||
&& $ConfigObject->Get('Ticket::Watcher')
|
||||
)
|
||||
{
|
||||
|
||||
$Self->Print("<yellow>Checking for archived tickets with ticket watcher entries...</yellow>\n");
|
||||
|
||||
# Find all archived tickets which have ticket seen flags set
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(ticket.id)
|
||||
FROM ticket
|
||||
INNER JOIN ticket_watcher ON ticket.id = ticket_watcher.ticket_id
|
||||
WHERE ticket.archive_flag = 1",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
my $Count = 0;
|
||||
ROW:
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
|
||||
$TicketObject->TicketWatchUnsubscribe(
|
||||
TicketID => $Row[0],
|
||||
AllUsers => 1,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
$Self->Print(" Removing ticket watcher entries of ticket $Count\n");
|
||||
|
||||
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done</green> (changed <yellow>$Count</yellow> tickets).\n");
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
134
Perl OTRS/Kernel/System/Console/Command/Maint/Ticket/Delete.pm
Normal file
134
Perl OTRS/Kernel/System/Console/Command/Maint/Ticket/Delete.pm
Normal file
@@ -0,0 +1,134 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::Delete;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Delete one or more tickets.');
|
||||
$Self->AddOption(
|
||||
Name => 'ticket-number',
|
||||
Description => "Specify one or more ticket numbers of tickets to be deleted.",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/.*/smx,
|
||||
Multiple => 1,
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'ticket-id',
|
||||
Description => "Specify one or more ticket ids of tickets to be deleted.",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/\d+/smx,
|
||||
Multiple => 1,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub PreRun {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my @TicketIDs = @{ $Self->GetOption('ticket-id') // [] };
|
||||
my @TicketNumbers = @{ $Self->GetOption('ticket-number') // [] };
|
||||
|
||||
if ( !@TicketIDs && !@TicketNumbers ) {
|
||||
die "Please provide option --ticket-id or --ticket-number.\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Deleting tickets...</yellow>\n");
|
||||
|
||||
my @TicketIDs = @{ $Self->GetOption('ticket-id') // [] };
|
||||
my @TicketNumbers = @{ $Self->GetOption('ticket-number') // [] };
|
||||
|
||||
my @DeleteTicketIDs;
|
||||
|
||||
TICKETNUMBER:
|
||||
for my $TicketNumber (@TicketNumbers) {
|
||||
|
||||
# lookup ticket id
|
||||
my $TicketID = $Kernel::OM->Get('Kernel::System::Ticket')->TicketIDLookup(
|
||||
TicketNumber => $TicketNumber,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
# error handling
|
||||
if ( !$TicketID ) {
|
||||
$Self->PrintError("Unable to find ticket number $TicketNumber.\n");
|
||||
next TICKETNUMBER;
|
||||
}
|
||||
|
||||
push @DeleteTicketIDs, $TicketID;
|
||||
}
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
# lookup ticket number
|
||||
my $TicketNumber = $Kernel::OM->Get('Kernel::System::Ticket')->TicketNumberLookup(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
# error handling
|
||||
if ( !$TicketNumber ) {
|
||||
$Self->PrintError("Unable to find ticket id $TicketID.\n");
|
||||
next TICKETID;
|
||||
}
|
||||
|
||||
push @DeleteTicketIDs, $TicketID;
|
||||
}
|
||||
|
||||
my $DeletedTicketCount = 0;
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@DeleteTicketIDs) {
|
||||
|
||||
# delete the ticket
|
||||
my $True = $Kernel::OM->Get('Kernel::System::Ticket')->TicketDelete(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
# error handling
|
||||
if ( !$True ) {
|
||||
$Self->PrintError("Unable to delete ticket with id $TicketID\n");
|
||||
next TICKETID;
|
||||
}
|
||||
|
||||
$Self->Print(" $TicketID\n");
|
||||
|
||||
# increase the deleted ticket count
|
||||
$DeletedTicketCount++;
|
||||
}
|
||||
|
||||
if ( !$DeletedTicketCount ) {
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
$Self->Print("<green>$DeletedTicketCount tickets have been deleted.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
136
Perl OTRS/Kernel/System/Console/Command/Maint/Ticket/Dump.pm
Normal file
136
Perl OTRS/Kernel/System/Console/Command/Maint/Ticket/Dump.pm
Normal file
@@ -0,0 +1,136 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::Dump;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Output::HTML::Layout',
|
||||
'Kernel::System::CommunicationChannel',
|
||||
'Kernel::System::Ticket',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Print a ticket and its articles to the console.');
|
||||
$Self->AddOption(
|
||||
Name => 'article-limit',
|
||||
Description => "Maximum number of articles to print.",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/\d+/smx,
|
||||
);
|
||||
$Self->AddArgument(
|
||||
Name => 'ticket-id',
|
||||
Description => "ID of the ticket to be printed.",
|
||||
Required => 1,
|
||||
ValueRegex => qr/\d+/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
|
||||
TicketID => $Self->GetArgument('ticket-id'),
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
if ( !%Ticket ) {
|
||||
$Self->PrintError("Could not find ticket.");
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
$Self->Print( "<green>" . ( '=' x 69 ) . "</green>\n" );
|
||||
|
||||
KEY:
|
||||
for my $Key (qw(TicketNumber TicketID Title Created Queue State Priority Lock CustomerID CustomerUserID)) {
|
||||
next KEY if !$Ticket{$Key};
|
||||
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$Key:", $Ticket{$Key} ) );
|
||||
}
|
||||
|
||||
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
|
||||
|
||||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
|
||||
# get article index
|
||||
my @MetaArticles = $ArticleObject->ArticleList(
|
||||
TicketID => $Self->GetArgument('ticket-id'),
|
||||
);
|
||||
|
||||
$Kernel::OM->ObjectParamAdd(
|
||||
'Kernel::Output::HTML::Layout' => {
|
||||
UserID => 1,
|
||||
},
|
||||
);
|
||||
## nofilter(TidyAll::Plugin::OTRS::Perl::LayoutObject)
|
||||
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
||||
|
||||
my $Counter = 1;
|
||||
my $ArticleLimit = $Self->GetOption('article-limit');
|
||||
META_ARTICLE:
|
||||
for my $MetaArticle (@MetaArticles) {
|
||||
|
||||
last META_ARTICLE if defined $ArticleLimit && $ArticleLimit < $Counter;
|
||||
|
||||
# get article data
|
||||
my %Article = $ArticleObject->BackendForArticle( %{$MetaArticle} )->ArticleGet(
|
||||
%{$MetaArticle},
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
next META_ARTICLE if !%Article;
|
||||
|
||||
my %CommunicationChannel = $Kernel::OM->Get('Kernel::System::CommunicationChannel')->ChannelGet(
|
||||
ChannelID => $Article{CommunicationChannelID},
|
||||
);
|
||||
$Article{Channel} = $CommunicationChannel{ChannelName};
|
||||
|
||||
KEY:
|
||||
for my $Key (qw(ArticleID CreateTime SenderType Channel)) {
|
||||
next KEY if !$Article{$Key};
|
||||
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$Key:", $Article{$Key} ) );
|
||||
}
|
||||
|
||||
my %ArticleFields = $LayoutObject->ArticleFields(%Article);
|
||||
|
||||
for my $ArticleFieldKey (
|
||||
sort { $ArticleFields{$a}->{Prio} <=> $ArticleFields{$b}->{Prio} }
|
||||
keys %ArticleFields
|
||||
)
|
||||
{
|
||||
my %ArticleField = %{ $ArticleFields{$ArticleFieldKey} // {} };
|
||||
$Self->Print( sprintf( "<yellow>%-20s</yellow> %s\n", "$ArticleField{Label}:", $ArticleField{Value} ) );
|
||||
}
|
||||
|
||||
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
|
||||
|
||||
my $ArticlePreview = $LayoutObject->ArticlePreview(
|
||||
%Article,
|
||||
ResultType => 'plain',
|
||||
);
|
||||
$Self->Print("$ArticlePreview\n");
|
||||
|
||||
$Self->Print( "<green>" . ( '-' x 69 ) . "</green>\n" );
|
||||
}
|
||||
continue {
|
||||
$Counter++;
|
||||
}
|
||||
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,217 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::EscalationCheck;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DateTime',
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Trigger ticket escalation events and notification events for escalation.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
# =item Run()
|
||||
#
|
||||
# looks for the tickets which will escalate within the next five days
|
||||
# then perform a search over the values on the TicketGet result
|
||||
# for checking if any of the escalations values are present, then base
|
||||
# on that the notification events for notifications and normal escalation
|
||||
# events are trigger
|
||||
#
|
||||
# NotificationEvents:
|
||||
# - NotificationEscalation
|
||||
# - NotificationEscalationNotifyBefore
|
||||
#
|
||||
# Escalation events:
|
||||
# - EscalationResponseTimeStart
|
||||
# - EscalationUpdateTimeStart
|
||||
# - EscalationSolutionTimeStart
|
||||
# - EscalationResponseTimeNotifyBefore
|
||||
# - EscalationUpdateTimeNotifyBefore
|
||||
# - EscalationSolutionTimeNotifyBefore
|
||||
#
|
||||
#
|
||||
# NotificationEvents are alway triggered, and Escalation events just
|
||||
# base on the 'OTRSEscalationEvents::DecayTime'.
|
||||
#
|
||||
# =cut
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Processing ticket escalation events ...</yellow>\n");
|
||||
|
||||
# get needed objects
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
|
||||
# the decay time is configured in minutes
|
||||
my $DecayTimeInSeconds = $Kernel::OM->Get('Kernel::Config')->Get('OTRSEscalationEvents::DecayTime') || 0;
|
||||
$DecayTimeInSeconds *= 60;
|
||||
|
||||
# check if it's a escalation or escalation notification
|
||||
# check escalation times
|
||||
my %TicketAttr2Event = (
|
||||
FirstResponseTimeEscalation => 'EscalationResponseTimeStart',
|
||||
UpdateTimeEscalation => 'EscalationUpdateTimeStart',
|
||||
SolutionTimeEscalation => 'EscalationSolutionTimeStart',
|
||||
FirstResponseTimeNotification => 'EscalationResponseTimeNotifyBefore',
|
||||
UpdateTimeNotification => 'EscalationUpdateTimeNotifyBefore',
|
||||
SolutionTimeNotification => 'EscalationSolutionTimeNotifyBefore',
|
||||
);
|
||||
|
||||
# Find all tickets which will escalate within the next five days.
|
||||
my @Tickets = $TicketObject->TicketSearch(
|
||||
Result => 'ARRAY',
|
||||
Limit => 1000,
|
||||
TicketEscalationTimeOlderMinutes => -( 5 * 24 * 60 ),
|
||||
Permission => 'rw',
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
TICKET:
|
||||
for my $TicketID (@Tickets) {
|
||||
|
||||
# get ticket data
|
||||
my %Ticket = $TicketObject->TicketGet(
|
||||
TicketID => $TicketID,
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
# get used calendar
|
||||
my $Calendar = $TicketObject->TicketCalendarGet(
|
||||
%Ticket,
|
||||
);
|
||||
|
||||
# check if it is during business hours, then send escalation info
|
||||
my $BusinessStartDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
$BusinessStartDTObject->Subtract( Seconds => 10 * 60 );
|
||||
|
||||
my $BusinessStopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
|
||||
my $CountedTime = $BusinessStartDTObject->Delta(
|
||||
DateTimeObject => $BusinessStopDTObject,
|
||||
ForWorkingTime => 1,
|
||||
Calendar => $Calendar,
|
||||
);
|
||||
|
||||
# don't trigger events if not counted time
|
||||
if ( !$CountedTime || !$CountedTime->{AbsoluteSeconds} ) {
|
||||
next TICKET;
|
||||
}
|
||||
|
||||
# needed for deciding whether events should be triggered
|
||||
my @HistoryLines = $TicketObject->HistoryGet(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
# check if it's a escalation of escalation notification
|
||||
# check escalation times
|
||||
my $EscalationType = 0;
|
||||
my @Events;
|
||||
TYPE:
|
||||
for my $Type (
|
||||
qw(FirstResponseTimeEscalation UpdateTimeEscalation SolutionTimeEscalation
|
||||
FirstResponseTimeNotification UpdateTimeNotification SolutionTimeNotification)
|
||||
)
|
||||
{
|
||||
next TYPE if !$Ticket{$Type};
|
||||
|
||||
my @ReversedHistoryLines = reverse @HistoryLines;
|
||||
|
||||
# get the last time this event was triggered
|
||||
# search in reverse order, as @HistoryLines sorted ascendingly by CreateTime
|
||||
if ($DecayTimeInSeconds) {
|
||||
|
||||
my $PrevEventLine = first { $_->{HistoryType} eq $TicketAttr2Event{$Type} }
|
||||
@ReversedHistoryLines;
|
||||
|
||||
if ( $PrevEventLine && $PrevEventLine->{CreateTime} ) {
|
||||
my $PrevEventTime = $Kernel::OM->Create(
|
||||
'Kernel::System::DateTime',
|
||||
ObjectParams => {
|
||||
String => $PrevEventLine->{CreateTime},
|
||||
},
|
||||
)->ToEpoch();
|
||||
|
||||
my $CurSysTime = $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch();
|
||||
|
||||
my $TimeSincePrevEvent = $CurSysTime - $PrevEventTime;
|
||||
|
||||
next TYPE if $TimeSincePrevEvent <= $DecayTimeInSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
# emit the event
|
||||
push @Events, $TicketAttr2Event{$Type};
|
||||
|
||||
if ( !$EscalationType ) {
|
||||
if ( $Type =~ /TimeEscalation$/ ) {
|
||||
push @Events, 'NotificationEscalation';
|
||||
$EscalationType = 1;
|
||||
}
|
||||
elsif ( $Type =~ /TimeNotification$/ ) {
|
||||
push @Events, 'NotificationEscalationNotifyBefore';
|
||||
$EscalationType = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EVENT:
|
||||
for my $Event (@Events) {
|
||||
|
||||
# trigger the event
|
||||
$TicketObject->EventHandler(
|
||||
Event => $Event,
|
||||
UserID => 1,
|
||||
Data => {
|
||||
TicketID => $TicketID,
|
||||
CustomerMessageParams => {
|
||||
TicketNumber => $Ticket{TicketNumber},
|
||||
},
|
||||
},
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
$Self->Print(
|
||||
"Ticket <yellow>$TicketID</yellow>: event <yellow>$Event</yellow>, processed.\n"
|
||||
);
|
||||
|
||||
if ( $Event eq 'NotificationEscalation' || $Event eq 'NotificationEscalationNotifyBefore' ) {
|
||||
next EVENT;
|
||||
}
|
||||
|
||||
# log the triggered event in the history
|
||||
$TicketObject->HistoryAdd(
|
||||
TicketID => $TicketID,
|
||||
HistoryType => $Event,
|
||||
Name => "%%$Event%%triggered",
|
||||
CreateUserID => 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,81 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::EscalationIndexRebuild;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Time::HiRes();
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Completely rebuild the ticket escalation index.');
|
||||
$Self->AddOption(
|
||||
Name => 'micro-sleep',
|
||||
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Rebuilding ticket escalation index...</yellow>\n");
|
||||
|
||||
# disable ticket events
|
||||
$Kernel::OM->Get('Kernel::Config')->{'Ticket::EventModulePost'} = {};
|
||||
|
||||
# get all tickets
|
||||
my @TicketIDs = $Kernel::OM->Get('Kernel::System::Ticket')->TicketSearch(
|
||||
Result => 'ARRAY',
|
||||
Limit => 100_000_000,
|
||||
UserID => 1,
|
||||
Permission => 'ro',
|
||||
);
|
||||
|
||||
my $Count = 0;
|
||||
my $MicroSleep = $Self->GetOption('micro-sleep');
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
$Count++;
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Ticket')->TicketEscalationIndexBuild(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
if ( $Count % 2000 == 0 ) {
|
||||
my $Percent = int( $Count / ( $#TicketIDs / 100 ) );
|
||||
$Self->Print(
|
||||
"<yellow>$Count</yellow> of <yellow>$#TicketIDs</yellow> processed (<yellow>$Percent %</yellow> done).\n"
|
||||
);
|
||||
}
|
||||
|
||||
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,108 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::FulltextIndex;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description(
|
||||
'Flag articles to automatically rebuild the article search index or displays the index status. Please use --status or --rebuild option, not both!'
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'status',
|
||||
Description => "Display the current status of the index.",
|
||||
Required => 0,
|
||||
HasValue => 0,
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'rebuild',
|
||||
Description => "Flag articles to automatically rebuild the article search index.",
|
||||
Required => 0,
|
||||
HasValue => 0,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub PreRun {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
if ( !$Self->GetOption('status') && !$Self->GetOption('rebuild') ) {
|
||||
$Self->Print( $Self->GetUsageHelp() );
|
||||
die "Either --status or --rebuild must be given!\n";
|
||||
}
|
||||
|
||||
if ( $Self->GetOption('status') && $Self->GetOption('rebuild') ) {
|
||||
$Self->Print( $Self->GetUsageHelp() );
|
||||
die "Either --status or --rebuild must be given, not both!\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
if ( $Self->GetOption('status') ) {
|
||||
$Self->Status();
|
||||
}
|
||||
|
||||
elsif ( $Self->GetOption('rebuild') ) {
|
||||
$Self->Rebuild();
|
||||
}
|
||||
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
sub Status {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my %Status = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexStatus();
|
||||
my $Percentage = $Status{ArticlesIndexed} / $Status{ArticlesTotal} * 100;
|
||||
|
||||
my $Color = $Percentage == 100 ? 'green' : 'yellow';
|
||||
|
||||
my $Output = sprintf(
|
||||
"Indexed Articles: <$Color>%.1f%%</$Color> (<$Color>%d/%d</$Color>)",
|
||||
$Percentage,
|
||||
$Status{ArticlesIndexed},
|
||||
$Status{ArticlesTotal}
|
||||
);
|
||||
|
||||
$Self->Print("$Output\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub Rebuild {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Marking all articles for reindexing...</yellow>\n");
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexRebuildFlagSet(
|
||||
All => 1,
|
||||
Value => 1,
|
||||
);
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,261 @@
|
||||
# --
|
||||
# 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.
|
||||
# --
|
||||
|
||||
## nofilter(TidyAll::Plugin::OTRS::Perl::NoExitInConsoleCommands)
|
||||
|
||||
package Kernel::System::Console::Command::Maint::Ticket::FulltextIndexRebuildWorker;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
use POSIX ":sys_wait_h";
|
||||
use Time::HiRes qw(sleep);
|
||||
use Kernel::System::VariableCheck qw(:all);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DB',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::PID',
|
||||
'Kernel::System::Ticket',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Rebuild the article search index for needed articles.');
|
||||
$Self->AddOption(
|
||||
Name => 'children',
|
||||
Description => "Specify the number of child processes to be used for indexing (default: 4, maximum: 20).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'limit',
|
||||
Description => "Maximum number of ArticleIDs to process (default: 20000).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'force-pid',
|
||||
Description => "Start even if another process is still registered in the database.",
|
||||
Required => 0,
|
||||
HasValue => 0,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub PreRun {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $Children = $Self->GetOption('children') // 4;
|
||||
|
||||
if ( $Children > 20 ) {
|
||||
die "The allowed maximum amount of child processes is 20!\n";
|
||||
}
|
||||
|
||||
my $ForcePID = $Self->GetOption('force-pid');
|
||||
|
||||
my $PIDObject = $Kernel::OM->Get('Kernel::System::PID');
|
||||
|
||||
my %PID = $PIDObject->PIDGet(
|
||||
Name => 'ArticleSearchIndexRebuild',
|
||||
);
|
||||
|
||||
if ( %PID && !$ForcePID ) {
|
||||
die "Active indexing process already running! Skipping...\n";
|
||||
}
|
||||
|
||||
my $Success = $PIDObject->PIDCreate(
|
||||
Name => 'ArticleSearchIndexRebuild',
|
||||
Force => $Self->GetOption('force-pid'),
|
||||
);
|
||||
|
||||
if ( !$Success ) {
|
||||
die "Unable to register indexing process! Skipping...\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $Children = $Self->GetOption('children') // 4;
|
||||
my $Limit = $Self->GetOption('limit') // 20000;
|
||||
|
||||
my %ArticleTicketIDs = $Kernel::OM->Get('Kernel::System::Ticket::Article')->ArticleSearchIndexRebuildFlagList(
|
||||
Value => 1,
|
||||
Limit => $Limit,
|
||||
);
|
||||
|
||||
# perform the indexing if needed
|
||||
if (%ArticleTicketIDs) {
|
||||
$Self->ArticleIndexRebuild(
|
||||
ArticleTicketIDs => \%ArticleTicketIDs,
|
||||
Children => $Children,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$Self->Print("<yellow>No indexing needed! Skipping...</yellow>\n");
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
sub ArticleIndexRebuild {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my @ArticleIDs = keys %{ $Param{ArticleTicketIDs} };
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::DB')->Disconnect();
|
||||
|
||||
# Destroy objects for the child processes.
|
||||
$Kernel::OM->ObjectsDiscard(
|
||||
Objects => [
|
||||
'Kernel::System::DB',
|
||||
],
|
||||
ForcePackageReload => 0,
|
||||
);
|
||||
|
||||
# Split ArticleIDs into equal arrays for the child processes.
|
||||
my @ArticleChunks;
|
||||
my $Count = 0;
|
||||
|
||||
for my $ArticleID (@ArticleIDs) {
|
||||
push @{ $ArticleChunks[ $Count++ % $Param{Children} ] }, $ArticleID;
|
||||
}
|
||||
|
||||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
|
||||
my %ActiveChildPID;
|
||||
|
||||
ARTICLEIDCHUNK:
|
||||
for my $ArticleIDChunk (@ArticleChunks) {
|
||||
|
||||
# Create a child process.
|
||||
my $PID = fork;
|
||||
|
||||
# Could not create child.
|
||||
if ( $PID < 0 ) {
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Unable to fork to a child process for article index rebuild!"
|
||||
);
|
||||
last ARTICLEIDCHUNK;
|
||||
}
|
||||
|
||||
# We're in the child process.
|
||||
if ( !$PID ) {
|
||||
|
||||
# Add the chunk of article data to the index.
|
||||
for my $ArticleID ( @{$ArticleIDChunk} ) {
|
||||
|
||||
my $Success = 0;
|
||||
|
||||
if (
|
||||
$ConfigObject->Get('Ticket::ArchiveSystem')
|
||||
&& !$ConfigObject->Get('Ticket::SearchIndex::IndexArchivedTickets')
|
||||
&& $TicketObject->TicketArchiveFlagGet( TicketID => $Param{ArticleTicketIDs}->{$ArticleID} )
|
||||
)
|
||||
{
|
||||
$Success = $ArticleObject->ArticleSearchIndexDelete(
|
||||
ArticleID => $ArticleID,
|
||||
UserID => 1,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$Success = $ArticleObject->ArticleSearchIndexBuild(
|
||||
TicketID => $Param{ArticleTicketIDs}->{$ArticleID},
|
||||
ArticleID => $ArticleID,
|
||||
UserID => 1,
|
||||
);
|
||||
}
|
||||
|
||||
if ( !$Success ) {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Could not rebuild index for ArticleID '$ArticleID'!"
|
||||
);
|
||||
}
|
||||
else {
|
||||
$ArticleObject->ArticleSearchIndexRebuildFlagSet(
|
||||
ArticleIDs => [$ArticleID],
|
||||
Value => 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# Close child process at the end.
|
||||
exit 0;
|
||||
}
|
||||
|
||||
$ActiveChildPID{$PID} = {
|
||||
PID => $PID,
|
||||
};
|
||||
}
|
||||
|
||||
# Check the status of all child processes every 0.1 seconds.
|
||||
# Wait for all child processes to be finished.
|
||||
WAIT:
|
||||
while (1) {
|
||||
|
||||
last WAIT if !%ActiveChildPID;
|
||||
|
||||
sleep 0.1;
|
||||
|
||||
PID:
|
||||
for my $PID ( sort keys %ActiveChildPID ) {
|
||||
|
||||
my $WaitResult = waitpid( $PID, WNOHANG );
|
||||
|
||||
if ( $WaitResult == -1 ) {
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Child process exited with errors: $?",
|
||||
);
|
||||
|
||||
delete $ActiveChildPID{$PID};
|
||||
|
||||
next PID;
|
||||
}
|
||||
|
||||
delete $ActiveChildPID{$PID} if $WaitResult;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub PostRun {
|
||||
my ($Self) = @_;
|
||||
|
||||
my $Success = $Kernel::OM->Get('Kernel::System::PID')->PIDDelete(
|
||||
Name => 'ArticleSearchIndexRebuild',
|
||||
);
|
||||
|
||||
if ( !$Success ) {
|
||||
$Self->PrintError("Unable to unregister indexing process! Skipping...\n");
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
return $Success;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,283 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::InvalidUserCleanup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DB',
|
||||
'Kernel::System::DateTime',
|
||||
'Kernel::System::Ticket',
|
||||
'Kernel::System::User',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description(
|
||||
'Delete ticket/article seen flags and ticket watcher entries of users which have been invalid for more than a month, and unlocks tickets by invalid agents immedately.'
|
||||
);
|
||||
$Self->AddOption(
|
||||
Name => 'micro-sleep',
|
||||
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Starting invalid user cleanup...</yellow>\n");
|
||||
|
||||
my $InvalidID = 2;
|
||||
|
||||
# Users must be invalid for at least one month
|
||||
my $InvalidBeforeDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
$InvalidBeforeDTObject->Subtract( Seconds => 60 * 60 * 24 * 31 );
|
||||
|
||||
# get user object
|
||||
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
|
||||
my $MicroSleep = $Self->GetOption('micro-sleep');
|
||||
|
||||
# First, find all invalid users which are invalid for more than one month
|
||||
my %AllUsers = $UserObject->UserList( Valid => 0 );
|
||||
my @CleanupInvalidUsers;
|
||||
my @CleanupInvalidUsersImmediately;
|
||||
USERID:
|
||||
for my $UserID ( sort keys %AllUsers ) {
|
||||
|
||||
my %User = $UserObject->GetUserData(
|
||||
UserID => $UserID,
|
||||
);
|
||||
|
||||
# Only take invalid users
|
||||
next USERID if ( $User{ValidID} != $InvalidID );
|
||||
|
||||
# Only take users which are invalid for more than one month
|
||||
my $InvalidTimeDTObject = $Kernel::OM->Create(
|
||||
'Kernel::System::DateTime',
|
||||
ObjectParams => {
|
||||
String => $User{ChangeTime},
|
||||
},
|
||||
);
|
||||
|
||||
push @CleanupInvalidUsersImmediately, \%User;
|
||||
|
||||
next USERID if ( $InvalidTimeDTObject >= $InvalidBeforeDTObject );
|
||||
|
||||
push @CleanupInvalidUsers, \%User;
|
||||
}
|
||||
|
||||
if ( !@CleanupInvalidUsersImmediately ) {
|
||||
$Self->Print("<green>No cleanup for invalid users is needed.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
$Self->_CleanupLocks(
|
||||
InvalidUsers => \@CleanupInvalidUsersImmediately,
|
||||
MicroSleep => $MicroSleep,
|
||||
);
|
||||
|
||||
if (@CleanupInvalidUsers) {
|
||||
$Self->_CleanupFlags(
|
||||
CleanupInvalidUsers => \@CleanupInvalidUsers,
|
||||
MicroSleep => $MicroSleep,
|
||||
);
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
sub _CleanupLocks {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my @Users = @{ $Param{InvalidUsers} };
|
||||
|
||||
$Self->Print( " Lock Cleanup for " . ( scalar @Users ) . " users starting...\n" );
|
||||
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
|
||||
my $StateMap = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::InvalidOwner::StateChange') // {};
|
||||
|
||||
my @TicketIDs = $TicketObject->TicketSearch(
|
||||
Result => 'ARRAY',
|
||||
Limit => 1_000_000,
|
||||
OwnerIDs => [ map { $_->{UserID} } @Users ],
|
||||
Locks => ['lock'],
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
my $StateCount = 0;
|
||||
for my $TicketID (@TicketIDs) {
|
||||
my %Ticket = $TicketObject->TicketGet(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
);
|
||||
$TicketObject->TicketLockSet(
|
||||
Lock => 'unlock',
|
||||
TicketID => $TicketID,
|
||||
UserID => $Ticket{OwnerID},
|
||||
SendNoNotification => 1,
|
||||
);
|
||||
if ( my $NewState = $StateMap->{ $Ticket{StateType} } ) {
|
||||
my $StateSet = $TicketObject->TicketStateSet(
|
||||
TicketID => $TicketID,
|
||||
State => $NewState,
|
||||
UserID => 1,
|
||||
);
|
||||
$StateCount++ if $StateSet;
|
||||
}
|
||||
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
|
||||
}
|
||||
$Self->Print(
|
||||
" <green>Done</green> (unlocked <yellow>"
|
||||
. @TicketIDs
|
||||
. "</yellow> and changed state of <yellow>$StateCount</yellow> tickets).\n"
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _CleanupFlags {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my @CleanupInvalidUsers = @{ $Param{CleanupInvalidUsers} };
|
||||
$Self->Print( " Flag Cleanup for " . ( scalar @CleanupInvalidUsers ) . " users starting...\n" );
|
||||
|
||||
# get needed objects
|
||||
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
my $ArticleObject = $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
|
||||
for my $User (@CleanupInvalidUsers) {
|
||||
|
||||
$Self->Print(" Checking for tickets with seen flags for user <yellow>$User->{UserLogin}</yellow>...\n");
|
||||
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(ticket.id)
|
||||
FROM ticket
|
||||
INNER JOIN ticket_flag ON ticket.id = ticket_flag.ticket_id
|
||||
WHERE ticket_flag.create_by = $User->{UserID}
|
||||
AND ticket_flag.ticket_key = 'Seen'",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
my @TicketIDs;
|
||||
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
|
||||
push @TicketIDs, $Row[0];
|
||||
}
|
||||
|
||||
my $Count = 0;
|
||||
for my $TicketID (@TicketIDs) {
|
||||
my $Delete = $TicketObject->TicketFlagDelete(
|
||||
TicketID => $TicketID,
|
||||
Key => 'Seen',
|
||||
UserID => $User->{UserID},
|
||||
);
|
||||
$Count++ if $Delete;
|
||||
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
|
||||
}
|
||||
|
||||
$Self->Print(
|
||||
" <green>Done</green> (changed <yellow>$Count</yellow> tickets for user <yellow>$User->{UserLogin}</yellow>).\n"
|
||||
);
|
||||
$Self->Print(" Checking for articles with seen flags for user <yellow>$User->{UserLogin}</yellow>...\n");
|
||||
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(article.id), article.ticket_id
|
||||
FROM article
|
||||
INNER JOIN ticket ON ticket.id = article.ticket_id
|
||||
INNER JOIN article_flag ON article.id = article_flag.article_id
|
||||
WHERE article_flag.create_by = $User->{UserID}
|
||||
AND article_flag.article_key = 'Seen'",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
my @IDs;
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @IDs,
|
||||
{
|
||||
ArticleID => $Row[0],
|
||||
TicketID => $Row[1],
|
||||
};
|
||||
}
|
||||
|
||||
$Count = 0;
|
||||
for my $ID (@IDs) {
|
||||
my $Delete = $ArticleObject->ArticleFlagDelete(
|
||||
ArticleID => $ID->{ArticleID},
|
||||
TicketID => $ID->{TicketID},
|
||||
Key => 'Seen',
|
||||
UserID => $User->{UserID},
|
||||
);
|
||||
$Count++ if $Delete;
|
||||
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
|
||||
}
|
||||
|
||||
$Self->Print(
|
||||
" <green>Done</green> (changed <yellow>$Count</yellow> articles for user <yellow>$User->{UserLogin}</yellow>).\n"
|
||||
);
|
||||
|
||||
if ( $ConfigObject->Get('Ticket::Watcher') ) {
|
||||
|
||||
$Self->Print(
|
||||
" Checking for tickets with ticket watcher entries for user <yellow>$User->{UserLogin}</yellow>...\n"
|
||||
);
|
||||
|
||||
return if !$DBObject->Prepare(
|
||||
SQL => "
|
||||
SELECT DISTINCT(ticket.id)
|
||||
FROM ticket
|
||||
INNER JOIN ticket_watcher ON ticket.id = ticket_watcher.ticket_id",
|
||||
Limit => 1_000_000,
|
||||
);
|
||||
|
||||
@TicketIDs = ();
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
push @TicketIDs, $Row[0];
|
||||
}
|
||||
|
||||
my $Count = 0;
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
my $Unsubscribe = $TicketObject->TicketWatchUnsubscribe(
|
||||
TicketID => $TicketID,
|
||||
WatchUserID => $User->{UserID},
|
||||
UserID => 1,
|
||||
);
|
||||
$Count++ if $$Unsubscribe;
|
||||
Time::HiRes::usleep( $Param{MicroSleep} ) if $Param{MicroSleep};
|
||||
}
|
||||
|
||||
$Self->Print(
|
||||
" <green>Done</green> (changed <yellow>$Count</yellow> tickets for user <yellow>$User->{UserLogin}</yellow>).\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
$Self->Print(" <green>Done.</green>\n");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,172 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::PendingCheck;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DateTime',
|
||||
'Kernel::System::State',
|
||||
'Kernel::System::Ticket',
|
||||
'Kernel::System::User',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Process pending tickets that are past their pending time and send pending reminders.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Process pending tickets...</yellow>\n");
|
||||
|
||||
# get needed objects
|
||||
my $StateObject = $Kernel::OM->Get('Kernel::System::State');
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
|
||||
my @TicketIDs;
|
||||
|
||||
my @PendingAutoStateIDs = $StateObject->StateGetStatesByType(
|
||||
Type => 'PendingAuto',
|
||||
Result => 'ID',
|
||||
);
|
||||
|
||||
if (@PendingAutoStateIDs) {
|
||||
|
||||
# do ticket auto jobs
|
||||
@TicketIDs = $TicketObject->TicketSearch(
|
||||
Result => 'ARRAY',
|
||||
StateIDs => [@PendingAutoStateIDs],
|
||||
SortBy => ['PendingTime'],
|
||||
OrderBy => ['Up'],
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
my %States = %{ $Kernel::OM->Get('Kernel::Config')->Get('Ticket::StateAfterPending') };
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
# get ticket data
|
||||
my %Ticket = $TicketObject->TicketGet(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
next TICKETID if $Ticket{UntilTime} >= 1;
|
||||
next TICKETID if !$States{ $Ticket{State} };
|
||||
|
||||
$Self->Print(
|
||||
" Update ticket state for ticket $Ticket{TicketNumber} ($TicketID) to '$States{$Ticket{State}}'..."
|
||||
);
|
||||
|
||||
# set new state
|
||||
my $Success = $TicketObject->StateSet(
|
||||
TicketID => $TicketID,
|
||||
State => $States{ $Ticket{State} },
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
# error handling
|
||||
if ( !$Success ) {
|
||||
$Self->Print(" failed.\n");
|
||||
next TICKETID;
|
||||
}
|
||||
|
||||
# get state type for new state
|
||||
my %State = $StateObject->StateGet(
|
||||
Name => $States{ $Ticket{State} },
|
||||
);
|
||||
if ( $State{TypeName} eq 'closed' ) {
|
||||
|
||||
# set new ticket lock
|
||||
$TicketObject->LockSet(
|
||||
TicketID => $TicketID,
|
||||
Lock => 'unlock',
|
||||
UserID => 1,
|
||||
Notification => 0,
|
||||
);
|
||||
}
|
||||
$Self->Print(" done.\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
$Self->Print(" No pending auto StateIDs found!\n");
|
||||
}
|
||||
|
||||
# do ticket reminder notification jobs
|
||||
@TicketIDs = $TicketObject->TicketSearch(
|
||||
Result => 'ARRAY',
|
||||
StateType => 'pending reminder',
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
# get ticket data
|
||||
my %Ticket = $TicketObject->TicketGet(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
DynamicFields => 0,
|
||||
);
|
||||
|
||||
next TICKETID if $Ticket{UntilTime} >= 1;
|
||||
|
||||
# get used calendar
|
||||
my $Calendar = $TicketObject->TicketCalendarGet(
|
||||
%Ticket,
|
||||
);
|
||||
|
||||
# check if it is during business hours, then send reminder
|
||||
my $StopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
my $StartDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
$StartDTObject->Subtract( Seconds => 10 * 60 );
|
||||
|
||||
my $CountedTime = $StartDTObject->Delta(
|
||||
DateTimeObject => $StopDTObject,
|
||||
ForWorkingTime => 1,
|
||||
Calendar => $Calendar,
|
||||
);
|
||||
|
||||
# error handling
|
||||
if ( !$CountedTime || !$CountedTime->{AbsoluteSeconds} ) {
|
||||
next TICKETID;
|
||||
}
|
||||
|
||||
# trigger notification event
|
||||
$TicketObject->EventHandler(
|
||||
Event => 'NotificationPendingReminder',
|
||||
Data => {
|
||||
TicketID => $Ticket{TicketID},
|
||||
CustomerMessageParams => {
|
||||
TicketNumber => $Ticket{TicketNumber},
|
||||
},
|
||||
},
|
||||
UserID => 1,
|
||||
);
|
||||
|
||||
$TicketObject->EventHandlerTransaction();
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,81 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::QueueIndexCleanup;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::DB',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Cleanup unneeded entries from StaticDB queue index.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub PreRun {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $Module = $Kernel::OM->Get('Kernel::Config')->Get('Ticket::IndexModule');
|
||||
if ( $Module =~ m{StaticDB} ) {
|
||||
my $Error = "$Module is the active queue index, aborting.\n";
|
||||
$Error .= "Use Maint::Ticket::QueueIndexRebuild to regenerate the active index.\n";
|
||||
die $Error;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Cleaning up ticket queue index...</yellow>\n");
|
||||
|
||||
my $DBObject = $Kernel::OM->Get('Kernel::System::DB');
|
||||
|
||||
my $Records;
|
||||
|
||||
$DBObject->Prepare(
|
||||
SQL => 'SELECT count(*) from ticket_index'
|
||||
);
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
$Records += $Row[0];
|
||||
}
|
||||
$DBObject->Prepare(
|
||||
SQL => 'SELECT count(*) from ticket_lock_index'
|
||||
);
|
||||
while ( my @Row = $DBObject->FetchrowArray() ) {
|
||||
$Records += $Row[0];
|
||||
}
|
||||
|
||||
if ( !$Records ) {
|
||||
$Self->Print("<green>Queue index is already clean.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::DB')->Do(
|
||||
SQL => 'DELETE FROM ticket_index',
|
||||
);
|
||||
|
||||
$DBObject->Do(
|
||||
SQL => 'DELETE FROM ticket_lock_index',
|
||||
);
|
||||
|
||||
$Self->Print("<green>Done ($Records records deleted).</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,40 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::QueueIndexRebuild;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Rebuild the ticket index for AgentTicketQueue.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Rebuilding ticket index...</yellow>\n");
|
||||
|
||||
if ( $Kernel::OM->Get('Kernel::System::Ticket')->TicketAcceleratorRebuild() ) {
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,96 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::RestoreFromArchive;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Time::HiRes();
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Restore non-closed tickets from the ticket archive.');
|
||||
$Self->AddOption(
|
||||
Name => 'micro-sleep',
|
||||
Description => "Specify microseconds to sleep after every ticket to reduce system load (e.g. 1000).",
|
||||
Required => 0,
|
||||
HasValue => 1,
|
||||
ValueRegex => qr/^\d+$/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Restoring tickets from ticket archive...</yellow>\n");
|
||||
|
||||
# disable ticket events
|
||||
$Kernel::OM->Get('Kernel::Config')->{'Ticket::EventModulePost'} = {};
|
||||
|
||||
# check if archive system is activated
|
||||
if ( !$Kernel::OM->Get('Kernel::Config')->Get('Ticket::ArchiveSystem') ) {
|
||||
$Self->Print("<green>No action required. The archive system is disabled at the moment.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
# get ticket object
|
||||
my $TicketObject = $Kernel::OM->Get('Kernel::System::Ticket');
|
||||
|
||||
# get all tickets with an archive flag and an open statetype
|
||||
my @TicketIDs = $TicketObject->TicketSearch(
|
||||
StateType => [ 'new', 'open', 'pending reminder', 'pending auto' ],
|
||||
ArchiveFlags => ['y'],
|
||||
Result => 'ARRAY',
|
||||
Limit => 100_000_000,
|
||||
UserID => 1,
|
||||
Permission => 'ro',
|
||||
);
|
||||
|
||||
my $TicketNumber = scalar @TicketIDs;
|
||||
my $Count = 1;
|
||||
my $MicroSleep = $Self->GetOption('micro-sleep');
|
||||
|
||||
TICKETID:
|
||||
for my $TicketID (@TicketIDs) {
|
||||
|
||||
# restore ticket from archive
|
||||
$TicketObject->TicketArchiveFlagSet(
|
||||
TicketID => $TicketID,
|
||||
UserID => 1,
|
||||
ArchiveFlag => 'n',
|
||||
);
|
||||
|
||||
# output state
|
||||
if ( $Count % 2000 == 0 ) {
|
||||
my $Percent = int( $Count / ( $TicketNumber / 100 ) );
|
||||
$Self->Print(
|
||||
"<yellow>$Count</yellow> of <yellow>$#TicketIDs</yellow> processed (<yellow>$Percent %</yellow> done).\n"
|
||||
);
|
||||
}
|
||||
|
||||
$Count++;
|
||||
|
||||
Time::HiRes::usleep($MicroSleep) if $MicroSleep;
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done ($TicketNumber tickets restored).</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,67 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::UnlockAll;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::DB',
|
||||
'Kernel::System::Lock',
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Unlock all tickets by force.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Unlocking all tickets...</yellow>\n");
|
||||
|
||||
my @ViewableLockIDs = $Kernel::OM->Get('Kernel::System::Lock')->LockViewableLock( Type => 'ID' );
|
||||
|
||||
my @Tickets;
|
||||
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
||||
SQL => "
|
||||
SELECT st.tn, st.id
|
||||
FROM ticket st
|
||||
WHERE st.ticket_lock_id NOT IN ( ${\(join ', ', @ViewableLockIDs)} ) ",
|
||||
);
|
||||
|
||||
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
||||
push @Tickets, \@Row;
|
||||
}
|
||||
for (@Tickets) {
|
||||
my @Row = @{$_};
|
||||
$Self->Print(" Unlocking ticket id $Row[0]... ");
|
||||
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
|
||||
TicketID => $Row[1],
|
||||
Lock => 'unlock',
|
||||
UserID => 1,
|
||||
);
|
||||
if ($Unlock) {
|
||||
$Self->Print("<green>done.</green>\n");
|
||||
}
|
||||
else {
|
||||
$Self->Print("<red>failed.</red>\n");
|
||||
}
|
||||
}
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,65 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::UnlockTicket;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Unlock a single ticket by force.');
|
||||
$Self->AddArgument(
|
||||
Name => 'ticket-id',
|
||||
Description => "Ticket to be unlocked by force.",
|
||||
Required => 1,
|
||||
ValueRegex => qr/\d+/smx,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my $TicketID = $Self->GetArgument('ticket-id');
|
||||
|
||||
$Self->Print("<yellow>Unlocking ticket $TicketID...</yellow>\n");
|
||||
|
||||
my %Ticket = $Kernel::OM->Get('Kernel::System::Ticket')->TicketGet(
|
||||
TicketID => $TicketID,
|
||||
Silent => 1,
|
||||
);
|
||||
|
||||
if ( !%Ticket ) {
|
||||
$Self->PrintError("Could not find ticket $TicketID.");
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
|
||||
TicketID => $TicketID,
|
||||
Lock => 'unlock',
|
||||
UserID => 1,
|
||||
);
|
||||
if ( !$Unlock ) {
|
||||
$Self->PrintError('Failed.');
|
||||
return $Self->ExitCodeError();
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,106 @@
|
||||
# --
|
||||
# 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::Console::Command::Maint::Ticket::UnlockTimeout;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent qw(Kernel::System::Console::BaseCommand);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::System::DateTime',
|
||||
'Kernel::System::DB',
|
||||
'Kernel::System::Lock',
|
||||
'Kernel::System::State',
|
||||
'Kernel::System::Ticket',
|
||||
);
|
||||
|
||||
sub Configure {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Description('Unlock tickets that are past their unlock timeout.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub Run {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
$Self->Print("<yellow>Unlocking tickets that are past their unlock timeout...</yellow>\n");
|
||||
|
||||
my @UnlockStateIDs = $Kernel::OM->Get('Kernel::System::State')->StateGetStatesByType(
|
||||
Type => 'Unlock',
|
||||
Result => 'ID',
|
||||
);
|
||||
my @ViewableLockIDs = $Kernel::OM->Get('Kernel::System::Lock')->LockViewableLock( Type => 'ID' );
|
||||
|
||||
my @Tickets;
|
||||
|
||||
$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
||||
SQL => "
|
||||
SELECT st.tn, st.id, st.timeout, sq.unlock_timeout, st.sla_id, st.queue_id
|
||||
FROM ticket st, queue sq
|
||||
WHERE st.queue_id = sq.id
|
||||
AND sq.unlock_timeout != 0
|
||||
AND st.ticket_state_id IN ( ${\(join ', ', @UnlockStateIDs)} )
|
||||
AND st.ticket_lock_id NOT IN ( ${\(join ', ', @ViewableLockIDs)} ) ",
|
||||
);
|
||||
|
||||
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
||||
push @Tickets, \@Row;
|
||||
}
|
||||
|
||||
TICKET:
|
||||
for (@Tickets) {
|
||||
my @Row = @{$_};
|
||||
|
||||
# get used calendar
|
||||
my $Calendar = $Kernel::OM->Get('Kernel::System::Ticket')->TicketCalendarGet(
|
||||
QueueID => $Row[5],
|
||||
SLAID => $Row[4],
|
||||
);
|
||||
|
||||
my $StartDTObject = $Kernel::OM->Create(
|
||||
'Kernel::System::DateTime',
|
||||
ObjectParams => {
|
||||
Epoch => $Row[2],
|
||||
},
|
||||
);
|
||||
|
||||
my $StopDTObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
||||
|
||||
my $StartStopDelta = $StartDTObject->Delta(
|
||||
DateTimeObject => $StopDTObject,
|
||||
ForWorkingTime => 1,
|
||||
Calendar => $Calendar,
|
||||
);
|
||||
|
||||
my $CountedTime = $StartStopDelta ? $StartStopDelta->{AbsoluteSeconds} : 0;
|
||||
|
||||
next TICKET if $CountedTime < $Row[3] * 60;
|
||||
|
||||
$Self->Print(" Unlocking ticket id $Row[0]... ");
|
||||
my $Unlock = $Kernel::OM->Get('Kernel::System::Ticket')->LockSet(
|
||||
TicketID => $Row[1],
|
||||
Lock => 'unlock',
|
||||
UserID => 1,
|
||||
);
|
||||
if ($Unlock) {
|
||||
$Self->Print("<green>done.</green>\n");
|
||||
}
|
||||
else {
|
||||
$Self->Print("<red>failed.</red>\n");
|
||||
}
|
||||
}
|
||||
|
||||
$Self->Print("<green>Done.</green>\n");
|
||||
return $Self->ExitCodeOk();
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user