993 lines
30 KiB
Perl
993 lines
30 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::ITSMChange::ITSMStateMachine;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
our @ObjectDependencies = (
|
|
'Kernel::Config',
|
|
'Kernel::System::Cache',
|
|
'Kernel::System::DB',
|
|
'Kernel::System::GeneralCatalog',
|
|
'Kernel::System::Log',
|
|
);
|
|
|
|
=head1 NAME
|
|
|
|
Kernel::System::ITSMChange::ITSMStateMachine - state machine lib
|
|
|
|
=head1 PUBLIC INTERFACE
|
|
|
|
=cut
|
|
|
|
=head2 new()
|
|
|
|
Create an object.
|
|
|
|
use Kernel::System::ObjectManager;
|
|
local $Kernel::OM = Kernel::System::ObjectManager->new();
|
|
my $StateMachineObject = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMStateMachine');
|
|
|
|
=cut
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {};
|
|
bless( $Self, $Type );
|
|
|
|
# set the debug flag
|
|
$Self->{Debug} = $Param{Debug} || 0;
|
|
|
|
# get the cache type and TTL (in seconds)
|
|
$Self->{CacheType} = 'ITSMStateMachine';
|
|
$Self->{CacheTTL} = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::CacheTTL') * 60;
|
|
|
|
return $Self;
|
|
}
|
|
|
|
=head2 StateTransitionAdd()
|
|
|
|
Add a new state transition. Returns the transition id on success.
|
|
|
|
my $TransitionID = $StateMachineObject->StateTransitionAdd(
|
|
StateID => 1, # id within the given class, or 0 to indicate the start state
|
|
NextStateID => 2, # id within the given class, or 0 to indicate an end state
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionAdd {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check if StateID and NextStateID are given (they can be 0)
|
|
for my $Argument (qw(StateID NextStateID)) {
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check that class is given
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check that not both StateID and NextStateID are zero
|
|
if ( !$Param{StateID} && !$Param{NextStateID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateID and NextStateID can't both be zero!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# define lookup hash for state name
|
|
my %StateID2State;
|
|
|
|
# check if StateID and NextStateID belong to the given class
|
|
ARGUMENT:
|
|
for my $Argument (qw(StateID NextStateID)) {
|
|
|
|
$StateID2State{ $Param{$Argument} } = $Self->StateLookup(
|
|
StateID => $Param{$Argument},
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# dont check zero values
|
|
next ARGUMENT if !$Param{$Argument};
|
|
|
|
# get class
|
|
my $DataRef = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
|
|
ItemID => $Param{$Argument},
|
|
);
|
|
|
|
# check if id belongs to given class
|
|
if ( !$DataRef || !%{$DataRef} || $DataRef->{Class} ne $Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "$Argument $Param{$Argument} is not in the class $Param{Class}!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check if a state transition with the StateID and NextStateID exists already
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id FROM change_state_machine '
|
|
. 'WHERE state_id = ? '
|
|
. 'AND next_state_id = ?',
|
|
Bind => [ \$Param{StateID}, \$Param{NextStateID} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
# fetch the result
|
|
my $TransitionID;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$TransitionID = $Row[0];
|
|
}
|
|
|
|
# do not insert this transition twice
|
|
return $TransitionID if $TransitionID;
|
|
|
|
# check if StateID is a start state (=0) and another start state already exists
|
|
if ( !$Param{StateID} ) {
|
|
|
|
# count the number of exsting start states in the given class
|
|
# ( the state_id 0 indicates that the next_state_id is a start state )
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT count(s.id) '
|
|
. 'FROM change_state_machine s, general_catalog g '
|
|
. 'WHERE g.general_catalog_class = ? '
|
|
. 'AND s.next_state_id = g.id '
|
|
. 'AND s.state_id = 0',
|
|
Bind => [ \$Param{Class} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
# fetch the result
|
|
my $Count;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$Count = $Row[0];
|
|
}
|
|
|
|
# if there is already a start state
|
|
if ($Count) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not add state '$StateID2State{$Param{NextStateID}}' "
|
|
. "(ID: $Param{NextStateID}) as start state. "
|
|
. "There is already a start state defined for class '$Param{Class}'!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# prevent setting an end state transition, if other state transistions exist already
|
|
if ( $Param{StateID} && !$Param{NextStateID} ) {
|
|
|
|
# check if other state transistions exist for the given StateID
|
|
my $NextStateIDs = $Self->StateTransitionGet(
|
|
StateID => $Param{StateID},
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# check if any next states are defined for this start state
|
|
if ( $NextStateIDs && @{$NextStateIDs} && scalar @{$NextStateIDs} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not set state "
|
|
. "'$StateID2State{$Param{StateID}}' (ID: $Param{StateID}) as end state, "
|
|
. "because other following states exist, which must be deleted first!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# prevent the adding of other next states if an end state is already defined
|
|
# for this start state
|
|
elsif ( $Param{StateID} && $Param{NextStateID} ) {
|
|
|
|
# check if other state transistions exist for the given StateID
|
|
my $NextStateIDs = $Self->StateTransitionGet(
|
|
StateID => $Param{StateID},
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# check if there is an end state (=0) defined for this start state
|
|
if ( $NextStateIDs && @{$NextStateIDs} && !$NextStateIDs->[0] ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "State '$StateID2State{$Param{StateID}}' "
|
|
. "(ID: $Param{StateID}) is defined as an end state, "
|
|
. "it must be deleted first, before new following states can be added!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# add state transition to database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'INSERT INTO change_state_machine '
|
|
. '(state_id, next_state_id) '
|
|
. 'VALUES (?, ?)',
|
|
Bind => [
|
|
\$Param{StateID}, \$Param{NextStateID},
|
|
],
|
|
);
|
|
|
|
# get TransitionID
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id FROM change_state_machine '
|
|
. 'WHERE state_id = ? '
|
|
. 'AND next_state_id = ?',
|
|
Bind => [ \$Param{StateID}, \$Param{NextStateID} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
# fetch the result
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$TransitionID = $Row[0];
|
|
}
|
|
|
|
# check if state transition could be added
|
|
if ( !$TransitionID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateTransitionAdd() failed!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# cleanup statemachine cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
|
|
Type => $Self->{CacheType},
|
|
);
|
|
|
|
return $TransitionID;
|
|
}
|
|
|
|
=head2 StateTransitionDelete()
|
|
|
|
Delete a state transition. Returns true on success.
|
|
|
|
my $Success = $StateMachineObject->StateTransitionDelete(
|
|
StateID => 1, # id within the given class, or 0 to indicate the start state
|
|
NextStateID => 2, # id within the given class, or 0 to indicate an end state
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionDelete {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check if StateID and NextStateID are given (they can be 0)
|
|
for my $Argument (qw(StateID NextStateID)) {
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# delete state transition from database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'DELETE FROM change_state_machine '
|
|
. 'WHERE state_id = ? AND next_state_id = ?',
|
|
Bind => [
|
|
\$Param{StateID}, \$Param{NextStateID},
|
|
],
|
|
);
|
|
|
|
# cleanup statemachine cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
|
|
Type => $Self->{CacheType},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 StateTransitionDeleteAll()
|
|
|
|
Delete all state transitions of a class. Returns true on success.
|
|
|
|
my $Success = $StateMachineObject->StateTransitionDeleteAll(
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionDeleteAll {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed parameter
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# find all state ids and next_state ids which belong to the given class
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT id '
|
|
. 'FROM general_catalog '
|
|
. 'WHERE general_catalog_class = ?',
|
|
Bind => [ \$Param{Class} ],
|
|
);
|
|
|
|
my @IDs;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @IDs, $Row[0];
|
|
}
|
|
|
|
# return if no state transitions exist for the given class
|
|
return 1 if !@IDs;
|
|
|
|
# build id string
|
|
my $IDString = join ', ', @IDs;
|
|
|
|
# delete state transition from database
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => "DELETE FROM change_state_machine "
|
|
. "WHERE state_id IN ( $IDString ) "
|
|
. "OR next_state_id IN ( $IDString )",
|
|
);
|
|
|
|
# cleanup statemachine cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
|
|
Type => $Self->{CacheType},
|
|
);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
=head2 StateTransitionGet()
|
|
|
|
Get a state transition for a given state id.
|
|
Returns an array reference of the next state ids.
|
|
|
|
my $NextStateIDsRef = $StateMachineObject->StateTransitionGet(
|
|
StateID => 1, # id within the given class, or 0 to indicate the start state
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionGet {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check if StateID are given (they can be 0)
|
|
for my $Argument (qw(StateID)) {
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check that class is given
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the cache
|
|
my $CacheKey = 'StateTransitionGet::StateID::' . $Param{StateID} . '::Class::' . $Param{Class};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return $Cache if $Cache;
|
|
|
|
# check if StateID belongs to the given class, but only if state id is not a start state (=0)
|
|
if ( $Param{StateID} ) {
|
|
|
|
# get class of given StateID
|
|
my $DataRef = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
|
|
ItemID => $Param{StateID},
|
|
);
|
|
|
|
# check if StateID belongs to given class
|
|
if ( !$DataRef || !%{$DataRef} || $DataRef->{Class} ne $Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateID $Param{StateID} is not in the class '$Param{Class}'!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# find all state ids and next_state ids which belong to the given class
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT DISTINCT s.next_state_id '
|
|
. 'FROM change_state_machine s '
|
|
. 'LEFT OUTER JOIN general_catalog g '
|
|
. 'ON ( (s.state_id = g.id ) OR (s.next_state_id = g.id) ) '
|
|
. 'WHERE s.state_id = ? AND g.general_catalog_class = ?',
|
|
Bind => [ \$Param{StateID}, \$Param{Class} ],
|
|
);
|
|
|
|
my @NextStateIDs;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @NextStateIDs, $Row[0];
|
|
}
|
|
|
|
# if the start state was requested and more than one start state was found
|
|
if ( !$Param{StateID} ) {
|
|
|
|
if ( !@NextStateIDs ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not get initial state for '$Param{Class}' "
|
|
. "No initial state was found!",
|
|
);
|
|
return;
|
|
}
|
|
if ( scalar @NextStateIDs > 1 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not get initial state for '$Param{Class}' "
|
|
. "More than one initial state was found!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# save values in cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@NextStateIDs,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
|
|
return \@NextStateIDs;
|
|
}
|
|
|
|
=head2 StateTransitionGetEndStates()
|
|
|
|
Get a state transition for a given state id, but only show the possible next end states.
|
|
Returns an array reference of the next end state ids.
|
|
|
|
my $NextStateIDsRef = $StateMachineObject->StateTransitionGetEndStates(
|
|
StateID => 1, # id within the given class, or 0 to indicate the start state
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionGetEndStates {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check if StateID are given (they can be 0)
|
|
for my $Argument (qw(StateID)) {
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check that class is given
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the cache
|
|
my $CacheKey = 'StateTransitionGetEndStates::StateID::' . $Param{StateID} . '::Class::' . $Param{Class};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return $Cache if $Cache;
|
|
|
|
# check if StateID belongs to the given class, but only if state id is not a start state (=0)
|
|
if ( $Param{StateID} ) {
|
|
|
|
# get class of given StateID
|
|
my $DataRef = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
|
|
ItemID => $Param{StateID},
|
|
);
|
|
|
|
# check if StateID belongs to given class
|
|
if ( !$DataRef || !%{$DataRef} || $DataRef->{Class} ne $Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateID $Param{StateID} is not in the class '$Param{Class}'!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# find all state ids and next_state ids which belong to the given class
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT DISTINCT s.next_state_id '
|
|
. 'FROM change_state_machine s '
|
|
. 'LEFT OUTER JOIN general_catalog g '
|
|
. 'ON ( (s.state_id = g.id ) OR (s.next_state_id = g.id) ) '
|
|
. 'WHERE s.state_id = ? AND g.general_catalog_class = ?',
|
|
Bind => [ \$Param{StateID}, \$Param{Class} ],
|
|
);
|
|
|
|
my @NextStateIDs;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @NextStateIDs, $Row[0];
|
|
}
|
|
|
|
# if the start state was requested and more than one start state was found
|
|
if ( !$Param{StateID} ) {
|
|
|
|
if ( !@NextStateIDs ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not get initial state for '$Param{Class}' "
|
|
. "No initial state was found!",
|
|
);
|
|
return;
|
|
}
|
|
if ( scalar @NextStateIDs > 1 ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not get initial state for '$Param{Class}' "
|
|
. "More than one initial state was found!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# build next state ids string
|
|
my $NextStateIDsString = join ', ', @NextStateIDs;
|
|
|
|
# find all next state ids which are end states
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT DISTINCT s.state_id '
|
|
. 'FROM change_state_machine s '
|
|
. "WHERE s.state_id IN ( $NextStateIDsString ) "
|
|
. 'AND s.next_state_id = 0',
|
|
);
|
|
|
|
my @NextEndStateIDs;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @NextEndStateIDs, $Row[0];
|
|
}
|
|
|
|
# save values in cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \@NextEndStateIDs,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
|
|
return \@NextEndStateIDs;
|
|
}
|
|
|
|
=head2 StateTransitionList()
|
|
|
|
Return a state transition list hash-array reference.
|
|
The hash key is the StateID, the hash value is an array reference of NextStateIDs.
|
|
|
|
my $StateTransitionsRef = $StateMachineObject->StateTransitionList(
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
Return example:
|
|
|
|
$StateTransitionsRef = {
|
|
0 => [ 1 ],
|
|
1 => [ 2, 3, 4 ],
|
|
2 => [ 5 ],
|
|
3 => [ 6, 7 ],
|
|
4 => [ 0 ],
|
|
5 => [ 0 ],
|
|
6 => [ 0 ],
|
|
7 => [ 0 ],
|
|
};
|
|
|
|
=cut
|
|
|
|
sub StateTransitionList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check the cache
|
|
my $CacheKey = 'StateTransitionList::Class::' . $Param{Class};
|
|
my $Cache = $Kernel::OM->Get('Kernel::System::Cache')->Get(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
);
|
|
return $Cache if $Cache;
|
|
|
|
# get state transitions
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT DISTINCT s.id , s.state_id , s.next_state_id , g.general_catalog_class '
|
|
. 'FROM change_state_machine s '
|
|
. 'LEFT OUTER JOIN general_catalog g '
|
|
. 'ON ( (s.state_id = g.id ) OR (s.next_state_id = g.id) ) '
|
|
. 'WHERE g.general_catalog_class = ?',
|
|
Bind => [ \$Param{Class} ],
|
|
);
|
|
|
|
my %StateTransition;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
push @{ $StateTransition{ $Row[1] } }, $Row[2];
|
|
}
|
|
|
|
# save values in cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->Set(
|
|
Type => $Self->{CacheType},
|
|
Key => $CacheKey,
|
|
Value => \%StateTransition,
|
|
TTL => $Self->{CacheTTL},
|
|
);
|
|
|
|
return \%StateTransition;
|
|
}
|
|
|
|
=head2 StateTransitionUpdate()
|
|
|
|
Update the next state of an existing new state transition.
|
|
Returns the transition id on success.
|
|
|
|
my $UpdateSuccess = $StateMachineObject->StateTransitionUpdate(
|
|
StateID => 1, # id within the given class, or 0 to indicate the start state
|
|
NextStateID => 2, # id within the given class, or 0 to indicate an end state
|
|
NewNextStateID => 3, # id within the given class, or 0 to indicate an end state
|
|
Class => 'ITSM::ChangeManagement::Change::State', # the name of a general catalog class
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateTransitionUpdate {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check if StateID, NextStateID and NewNextStateID are given (they can be 0)
|
|
for my $Argument (qw(StateID NextStateID NewNextStateID)) {
|
|
if ( !defined $Param{$Argument} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Argument!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# check that class is given
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check that not both StateID and NextStateID are zero
|
|
if ( !$Param{StateID} && !$Param{NextStateID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateID and NextStateID can't both be zero!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check that not both StateID and NewNextStateID are zero
|
|
if ( !$Param{StateID} && !$Param{NewNextStateID} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "StateID and NewNextStateID can't both be zero!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# define lookup hash for state name
|
|
my %StateID2State;
|
|
|
|
# check if StateID, NextStateID and NewNextStateID belong to the given class
|
|
ARGUMENT:
|
|
for my $Argument (qw(StateID NextStateID NewNextStateID)) {
|
|
|
|
$StateID2State{ $Param{$Argument} } = $Self->StateLookup(
|
|
StateID => $Param{$Argument},
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# dont check zero values
|
|
next ARGUMENT if !$Param{$Argument};
|
|
|
|
# get class
|
|
my $DataRef = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemGet(
|
|
ItemID => $Param{$Argument},
|
|
);
|
|
|
|
# check if id belongs to given class
|
|
if ( !$DataRef || !%{$DataRef} || $DataRef->{Class} ne $Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "$Argument $Param{$Argument} is not in the class '$Param{Class}'!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# do not update if the new next state is the same
|
|
return 1 if $Param{NextStateID} == $Param{NewNextStateID};
|
|
|
|
# get the existing state transition id that should be updated
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Prepare(
|
|
SQL => 'SELECT DISTINCT s.id '
|
|
. 'FROM change_state_machine s '
|
|
. 'LEFT OUTER JOIN general_catalog g '
|
|
. 'ON ( (s.state_id = g.id ) OR (s.next_state_id = g.id) ) '
|
|
. 'WHERE s.state_id = ? AND s.next_state_id = ? '
|
|
. 'AND g.general_catalog_class = ?',
|
|
Bind => [ \$Param{StateID}, \$Param{NextStateID}, \$Param{Class} ],
|
|
Limit => 1,
|
|
);
|
|
|
|
my $TransitionID;
|
|
while ( my @Row = $Kernel::OM->Get('Kernel::System::DB')->FetchrowArray() ) {
|
|
$TransitionID = $Row[0];
|
|
}
|
|
|
|
# check that the state transition that should be updated exists
|
|
if ( !$TransitionID ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not update state transition! A state transition with "
|
|
. "StateID $Param{StateID} and NextStateID $Param{NextStateID} does not exist!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# prevent setting an end state transition, if other state transistions exist already
|
|
if ( !$Param{NewNextStateID} ) {
|
|
|
|
# check if other state transistions exist for the given StateID
|
|
my $NextStateIDs = $Self->StateTransitionGet(
|
|
StateID => $Param{StateID},
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# The old state transition, which is verified to exist, does not count in this check
|
|
if ( $NextStateIDs && scalar @{$NextStateIDs} > 1 ) {
|
|
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Can not set state "
|
|
. "'$StateID2State{$Param{StateID}}' (ID: $Param{StateID}) as end state, "
|
|
. "because other following states exist, which must be deleted first!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# update state transition
|
|
return if !$Kernel::OM->Get('Kernel::System::DB')->Do(
|
|
SQL => 'UPDATE change_state_machine '
|
|
. 'SET next_state_id = ? '
|
|
. 'WHERE id = ?',
|
|
Bind => [ \$Param{NewNextStateID}, \$TransitionID ],
|
|
);
|
|
|
|
# cleanup statemachine cache
|
|
$Kernel::OM->Get('Kernel::System::Cache')->CleanUp(
|
|
Type => $Self->{CacheType},
|
|
);
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 StateLookup()
|
|
|
|
This method does a lookup for a state. If a state id is given,
|
|
it returns the name of the state. If a state name is given,
|
|
the appropriate id is returned.
|
|
|
|
my $State = $StateMachineObject->StateLookup(
|
|
StateID => 1234,
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
);
|
|
|
|
my $StateID = $StateMachineObject->StateLookup(
|
|
State => 'accepted',
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
);
|
|
|
|
=cut
|
|
|
|
sub StateLookup {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check Class parameter
|
|
if ( !$Param{Class} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Class!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# The StateID '0' is a special case. Depending on the context it
|
|
# can indicate '*START*' or '*END*'. So return '0' in that case,
|
|
# and do not flood the error log.
|
|
if (
|
|
defined $Param{StateID}
|
|
&& $Param{StateID} eq '0'
|
|
&& !defined $Param{State}
|
|
)
|
|
{
|
|
return '0';
|
|
}
|
|
|
|
# The State '0' is a special case. Depending on the context it
|
|
# can indicate '*START*' or '*END*'. So return '0' in that case,
|
|
# and do not flood the error log.
|
|
if (
|
|
defined $Param{State}
|
|
&& $Param{State} eq '0'
|
|
&& !defined $Param{StateID}
|
|
)
|
|
{
|
|
return '0';
|
|
}
|
|
|
|
# either StateID or State must be passed
|
|
if ( !$Param{StateID} && !$Param{State} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need StateID or State!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# only one parameter State or StateID is allowed
|
|
if ( $Param{StateID} && $Param{State} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Need StateID OR State - not both!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# get the change states from the general catalog
|
|
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => $Param{Class},
|
|
);
|
|
|
|
# convert state list into a lookup hash
|
|
my %StateID2Name;
|
|
if ( $StateList && ref $StateList eq 'HASH' && %{$StateList} ) {
|
|
%StateID2Name = %{$StateList};
|
|
}
|
|
|
|
# check the state hash
|
|
if ( !%StateID2Name ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => 'Could not retrieve change states from the general catalog.',
|
|
);
|
|
return;
|
|
}
|
|
if ( $Param{StateID} ) {
|
|
return $StateID2Name{ $Param{StateID} };
|
|
}
|
|
else {
|
|
|
|
# reverse key - value pairs to have the name as keys
|
|
my %StateName2ID = reverse %StateID2Name;
|
|
|
|
return $StateName2ID{ $Param{State} };
|
|
}
|
|
}
|
|
|
|
=head2 StateList()
|
|
|
|
This method returns a list of states for a catalog class.
|
|
|
|
my $StateList = $StateMachineObject->StateList(
|
|
Class => 'ITSM::ChangeManagement::Change::State',
|
|
UserID => 1,
|
|
);
|
|
|
|
The return value is a reference to an array of hashrefs. The element 'Key' is then
|
|
the state id and the element 'Value' is the name of the state. The array elements
|
|
are sorted by state id.
|
|
|
|
my $StateList = [
|
|
{
|
|
Key => 156,
|
|
Value => 'approved',
|
|
},
|
|
{
|
|
Key => 157,
|
|
Value => 'in progress',
|
|
},
|
|
];
|
|
|
|
=cut
|
|
|
|
sub StateList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check needed stuff
|
|
for my $Attribute (qw(Class UserID)) {
|
|
if ( !$Param{$Attribute} ) {
|
|
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
|
Priority => 'error',
|
|
Message => "Need $Attribute!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# get change state list
|
|
my $StateList = $Kernel::OM->Get('Kernel::System::GeneralCatalog')->ItemList(
|
|
Class => $Param{Class},
|
|
) || {};
|
|
|
|
# to store an array of hash refs
|
|
my @ArrayHashRef;
|
|
|
|
# assemble the array of hash refs with all states
|
|
for my $StateID ( sort keys %{$StateList} ) {
|
|
push @ArrayHashRef, {
|
|
Key => $StateID,
|
|
Value => $StateList->{$StateID},
|
|
};
|
|
}
|
|
|
|
return \@ArrayHashRef;
|
|
}
|
|
|
|
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
|