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

686 lines
23 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::Event::HistoryAdd;
use strict;
use warnings;
our @ObjectDependencies = (
'Kernel::System::ITSMChange::History',
'Kernel::System::ITSMChange::ITSMWorkOrder',
'Kernel::System::Log',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
return $Self;
}
sub Run {
my ( $Self, %Param ) = @_;
# check needed stuff
for my $Needed (qw(Data Event Config UserID)) {
if ( !$Param{$Needed} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need $Needed!",
);
return;
}
}
# do not modify the original event, because we need this unmodified in later event modules
my $Event = $Param{Event};
# in history event handling we use Event name without the trailing 'Post'
$Event =~ s{ Post \z }{}xms;
# distinguish between Change and WorkOrder events, based on naming convention
my ($Type) = $Event =~ m{ \A ( Change | WorkOrder | Condition | Expression | Action ) }xms;
if ( !$Type ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Could not determine the object type for the event '$Event'!"
);
return;
}
# store all history add data
my @HistoryAddData;
# do history stuff
if ( $Event eq 'ChangeAdd' || $Event eq 'WorkOrderAdd' ) {
# tell history that a change was added
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ $Type . 'ID' },
UserID => $Param{UserID},
};
}
elsif ( $Event eq 'ChangeUpdate' || $Event eq 'WorkOrderUpdate' ) {
# get old data, either from change or workorder
my $OldData = $Param{Data}->{"Old${Type}Data"};
my $ChangeID = $OldData->{ChangeID}; # works for change and workorder events
FIELD:
for my $Field ( sort keys %{ $Param{Data} } ) {
# do not track special fields 'OldChangeData' or 'OldWorkOrderData'
next FIELD if $Field eq "Old${Type}Data";
# we do not track the user id
next FIELD if $Field eq 'UserID';
# we do not track the "plain" columns, only the non-plain columns
next FIELD if $Field eq 'JustificationPlain'; # change
next FIELD if $Field eq 'DescriptionPlain'; # change
next FIELD if $Field eq 'ReportPlain'; # workorder
next FIELD if $Field eq 'InstructionPlain'; # workorder
# we do no want to track the internal field "NoNumberCalc"
next FIELD if $Field eq 'NoNumberCalc'; # workorder
# The history of CAB updates is not tracked here,
# but in the handler for ChangeCABUpdate.
next FIELD if $Field eq 'CABAgents'; # change
next FIELD if $Field eq 'CABCustomers'; # change
# special handling for accounted time
if ( $Type eq 'WorkOrder' && $Field eq 'AccountedTime' ) {
# we do not track if accounted time was empty
next FIELD if !$Param{Data}->{$Field};
# if accounted time is not empty, we always track the history
# get workorder data
my $WorkOrder = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderGet(
WorkOrderID => $Param{Data}->{WorkOrderID},
UserID => $Param{UserID},
);
# save history if accounted time has changed
push @HistoryAddData, {
ChangeID => $ChangeID,
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
Fieldname => $Field,
ContentNew => $WorkOrder->{$Field},
ContentOld => $OldData->{$Field},
UserID => $Param{UserID},
};
next FIELD;
}
# check if field has changed
my $FieldHasChanged = $Self->_HasFieldChanged(
New => $Param{Data}->{$Field},
Old => $OldData->{$Field},
);
# save history if field has changed
if ($FieldHasChanged) {
push @HistoryAddData, {
ChangeID => $ChangeID,
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
Fieldname => $Field,
ContentNew => $Param{Data}->{$Field},
ContentOld => $OldData->{$Field},
UserID => $Param{UserID},
};
}
}
}
elsif ( $Event eq 'WorkOrderDelete' ) {
# get old data
my $OldData = $Param{Data}->{OldWorkOrderData};
# get existing history entries for this workorder
my $HistoryEntries = $Kernel::OM->Get('Kernel::System::ITSMChange::History')->WorkOrderHistoryGet(
WorkOrderID => $OldData->{WorkOrderID},
UserID => $Param{UserID},
);
# update history entries: delete workorder id
HISTORYENTRY:
for my $HistoryEntry ( @{$HistoryEntries} ) {
$Kernel::OM->Get('Kernel::System::ITSMChange::History')->HistoryUpdate(
HistoryEntryID => $HistoryEntry->{HistoryEntryID},
WorkOrderID => undef,
UserID => $Param{UserID},
);
}
# add history entry for WorkOrder deletion
# call HistoryAdd directly from here instead of using the @HistoryAddData
# as we want this to appear next to the line in history that HistoryUpdate
# just added in the code block before
return if !$Kernel::OM->Get('Kernel::System::ITSMChange::History')->HistoryAdd(
ChangeID => $OldData->{ChangeID},
HistoryType => $Event,
ContentNew => $OldData->{WorkOrderID},
UserID => $Param{UserID},
);
}
# handle ChangeCAB events
elsif ( $Event eq 'ChangeCABUpdate' || $Event eq 'ChangeCABDelete' ) {
# get old data
my $OldData = $Param{Data}->{OldChangeCABData};
FIELD:
for my $Field (qw(CABAgents CABCustomers)) {
# we do not track when the param has not been passed
next FIELD if !$Param{Data}->{$Field};
# check if field has changed
my $FieldHasChanged = $Self->_HasFieldChanged(
New => $Param{Data}->{$Field},
Old => $OldData->{$Field},
);
# save history if field has changed
if ($FieldHasChanged) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $Field,
ContentNew => join( '%%', @{ $Param{Data}->{$Field} } ),
ContentOld => join( '%%', @{ $OldData->{$Field} } ),
UserID => $Param{UserID},
};
}
}
}
# handle link events
elsif (
$Event eq 'ChangeLinkAdd'
|| $Event eq 'ChangeLinkDelete'
|| $Event eq 'WorkOrderLinkAdd'
|| $Event eq 'WorkOrderLinkDelete'
)
{
# for workorder links get the change id
if ( $Param{Data}->{WorkOrderID} ) {
my $WorkOrder = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder')->WorkOrderGet(
WorkOrderID => $Param{Data}->{WorkOrderID},
UserID => $Param{UserID},
);
$Param{Data}->{ChangeID} = $WorkOrder->{ChangeID};
}
my $ContentNew = join '%%',
$Param{Data}->{SourceObject} || $Param{Data}->{TargetObject},
$Param{Data}->{SourceKey} || $Param{Data}->{TargetKey};
# tell history that a link was added
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
ContentNew => $ContentNew,
UserID => $Param{UserID},
};
}
# handle attachment events
elsif (
$Event eq 'ChangeAttachmentAdd'
|| $Event eq 'ChangeAttachmentDelete'
|| $Event eq 'WorkOrderAttachmentAdd'
|| $Event eq 'WorkOrderAttachmentDelete'
|| $Event eq 'WorkOrderReportAttachmentAdd'
|| $Event eq 'WorkOrderReportAttachmentDelete'
)
{
# tell history that an attachment event was triggered
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
ContentNew => $Param{Data}->{Filename},
UserID => $Param{UserID},
};
}
# handle xxxTimeReached events
elsif ( $Event =~ m{ TimeReached \z }xms ) {
# get either WorkOrderID or ChangeID
my $ID = $Param{Data}->{WorkOrderID} || $Param{Data}->{ChangeID};
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
ContentNew => $ID . '%%Notification Sent',
UserID => $Param{UserID},
};
}
# add history entry when notification was sent
elsif ( $Event =~ m{ NotificationSent \z }xms ) {
# get either WorkOrderID or ChangeID
my $ID = $Param{Data}->{WorkOrderID} || $Param{Data}->{ChangeID};
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
WorkOrderID => $Param{Data}->{WorkOrderID},
HistoryType => $Event,
ContentNew => $Param{Data}->{To} . '%%' . $Param{Data}->{EventType},
UserID => $Param{UserID},
};
}
# handle condition events
elsif ( $Event eq 'ConditionAdd' ) {
# create history for condition
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ConditionID},
UserID => $Param{UserID},
};
# create history for all condition fields
my @ConditionStatic = qw(ConditionID UserID ChangeID);
CONDITIONFIELD:
for my $ConditionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next CONDITIONFIELD if grep { $_ eq $ConditionField } @ConditionStatic;
# do not add empty fields to history
next CONDITIONFIELD if !$Param{Data}->{$ConditionField};
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $ConditionField,
ContentNew => $Param{Data}->{$ConditionField},
UserID => $Param{UserID},
};
}
}
# handle condition update events
elsif ( $Event eq 'ConditionUpdate' ) {
# get old data
my $OldData = $Param{Data}->{OldConditionData};
# create history for all condition fields
my @ConditionStatic = qw(ConditionID UserID ChangeID OldConditionData);
CONDITIONFIELD:
for my $ConditionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next CONDITIONFIELD if grep { $_ eq $ConditionField } @ConditionStatic;
# do not add empty fields to history
next CONDITIONFIELD if !$Param{Data}->{$ConditionField};
# check if field has changed
my $FieldHasChanged = $Self->_HasFieldChanged(
New => $Param{Data}->{$ConditionField},
Old => $OldData->{$ConditionField},
);
# create history only for changed fields
next CONDITIONFIELD if !$FieldHasChanged;
push @HistoryAddData, {
ChangeID => $OldData->{ChangeID},
HistoryType => $Event,
Fieldname => $ConditionField,
ContentNew => $Param{Data}->{ConditionID} . '%%' . $Param{Data}->{$ConditionField},
ContentOld => $Param{Data}->{ConditionID} . '%%' . $OldData->{$ConditionField},
UserID => $Param{UserID},
};
}
}
# handle condition delete events
elsif ( $Event eq 'ConditionDelete' ) {
# get old data
my $OldData = $Param{Data}->{OldConditionData};
push @HistoryAddData, {
ChangeID => $OldData->{ChangeID},
HistoryType => $Event,
ContentNew => $OldData->{ConditionID},
UserID => $Param{UserID},
};
}
# handle condition delete events
elsif ( $Event eq 'ConditionDeleteAll' ) {
return if !$Kernel::OM->Get('Kernel::System::ITSMChange::History')->HistoryAdd(
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ChangeID},
UserID => $Param{UserID},
);
}
# handle expression events
elsif ( $Event eq 'ExpressionAdd' ) {
# create history for expression
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ExpressionID},
UserID => $Param{UserID},
};
# create history for all expression fields
my @ExpressionStatic = qw( ExpressionID UserID ChangeID);
EXPRESSIONFIELD:
for my $ExpressionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next EXPRESSIONFIELD if grep { $_ eq $ExpressionField } @ExpressionStatic;
# do not add empty fields to history
next EXPRESSIONFIELD if !$Param{Data}->{$ExpressionField};
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $ExpressionField,
ContentNew => $Param{Data}->{$ExpressionField},
UserID => $Param{UserID},
};
}
}
# handle expression update events
elsif ( $Event eq 'ExpressionUpdate' ) {
# get old data
my $OldData = $Param{Data}->{OldExpressionData};
# create history for all expression fields
my @ExpressionStatic = qw( ExpressionID UserID ChangeID OldExpressionData );
EXPRESSIONFIELD:
for my $ExpressionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next EXPRESSIONFIELD if grep { $_ eq $ExpressionField } @ExpressionStatic;
# do not add empty fields to history
next EXPRESSIONFIELD if !$Param{Data}->{$ExpressionField};
# check if field has changed
my $FieldHasChanged = $Self->_HasFieldChanged(
New => $Param{Data}->{$ExpressionField},
Old => $OldData->{$ExpressionField},
);
# create history only for changed fields
next EXPRESSIONFIELD if !$FieldHasChanged;
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $ExpressionField,
ContentNew => $Param{Data}->{ExpressionID} . '%%'
. $Param{Data}->{$ExpressionField},
ContentOld => $Param{Data}->{ExpressionID} . '%%' . $OldData->{$ExpressionField},
UserID => $Param{UserID},
};
}
}
# handle expression delete events
elsif ( $Event eq 'ExpressionDelete' ) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ExpressionID},
UserID => $Param{UserID},
};
}
# handle delete all expressions events
elsif ( $Event eq 'ExpressionDeleteAll' ) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ConditionID},
UserID => $Param{UserID},
};
}
# handle action events
elsif ( $Event eq 'ActionAdd' ) {
# create history for action
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ActionID},
UserID => $Param{UserID},
};
# create history for all action fields
my @ActionStatic = qw( ActionID UserID ChangeID);
ACTIONFIELD:
for my $ActionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next ACTIONFIELD if grep { $_ eq $ActionField } @ActionStatic;
# do not add empty fields to history
next ACTIONFIELD if !$Param{Data}->{$ActionField};
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $ActionField,
ContentNew => $Param{Data}->{$ActionField},
UserID => $Param{UserID},
};
}
}
# handle action update events
elsif ( $Event eq 'ActionUpdate' ) {
# get old data
my $OldData = $Param{Data}->{OldActionData};
# create history for all expression fields
my @ActionStatic = qw( ActionID UserID ChangeID OldActionData );
ACTIONFIELD:
for my $ActionField ( sort keys %{ $Param{Data} } ) {
# check for static fields
next ACTIONFIELD if grep { $_ eq $ActionField } @ActionStatic;
# do not add empty fields to history
next ACTIONFIELD if !$Param{Data}->{$ActionField};
# check if field has changed
my $FieldHasChanged = $Self->_HasFieldChanged(
New => $Param{Data}->{$ActionField},
Old => $OldData->{$ActionField},
);
# create history only for changed fields
next ACTIONFIELD if !$FieldHasChanged;
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
Fieldname => $ActionField,
ContentNew => $Param{Data}->{ActionID} . '%%' . $Param{Data}->{$ActionField},
ContentOld => $Param{Data}->{ActionID} . '%%' . $OldData->{$ActionField},
UserID => $Param{UserID},
};
}
}
# handle action delete events
elsif ( $Event eq 'ActionDelete' ) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ActionID},
UserID => $Param{UserID},
};
}
# handle delete all actions events
elsif ( $Event eq 'ActionDeleteAll' ) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ConditionID},
UserID => $Param{UserID},
};
}
# handle action execute events
elsif ( $Event eq 'ActionExecute' ) {
push @HistoryAddData, {
ChangeID => $Param{Data}->{ChangeID},
HistoryType => $Event,
ContentNew => $Param{Data}->{ActionID} . '%%' . $Param{Data}->{ActionResult},
UserID => $Param{UserID},
};
}
# error
else {
# an unknown event
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "$Event is an unknown event!",
);
return;
}
# if there is nothing to write to the history
return 1 if !@HistoryAddData;
# if there is just one history entry to write
if ( scalar @HistoryAddData == 1 ) {
# write the first and only entry of the array to the history
$Kernel::OM->Get('Kernel::System::ITSMChange::History')->HistoryAdd(
%{ $HistoryAddData[0] },
);
}
# there is more than one entry to write
# let the HistoryAddMultiple function handle that
else {
$Kernel::OM->Get('Kernel::System::ITSMChange::History')->HistoryAddMultiple(
Data => \@HistoryAddData,
);
}
return 1;
}
=head1 PRIVATE INTERFACE
=head2 _HasFieldChanged()
This method checks whether a field was changed or not. It returns 1 when field
was changed, undef otherwise.
my $FieldHasChanged = $HistoryObject->_HasFieldChanged(
Old => 'old value', # can be array reference or hash reference as well
New => 'new value', # can be array reference or hash reference as well
);
=cut
sub _HasFieldChanged {
my ( $Self, %Param ) = @_;
# field has changed when either 'new' or 'old is not set
return 1 if !( $Param{New} && $Param{Old} ) && ( $Param{New} || $Param{Old} );
# field has not changed when both values are empty
return if !$Param{New} && !$Param{Old};
# return result of 'eq' when both params are scalars
return $Param{New} ne $Param{Old} if !ref( $Param{New} ) && !ref( $Param{Old} );
# a field has changed when 'ref' is different
return 1 if ref( $Param{New} ) ne ref( $Param{Old} );
# check hashes
if ( ref $Param{New} eq 'HASH' ) {
# field has changed when number of keys are different
return 1 if scalar keys %{ $Param{New} } != scalar keys %{ $Param{Old} };
# check the values for each key
for my $Key ( sort keys %{ $Param{New} } ) {
return 1 if $Param{New}->{$Key} ne $Param{Old}->{$Key};
}
}
# check arrays
if ( ref $Param{New} eq 'ARRAY' ) {
# changed when number of elements differ
return 1 if scalar @{ $Param{New} } != scalar @{ $Param{Old} };
# check each element
for my $Index ( 0 .. $#{ $Param{New} } ) {
return 1 if $Param{New}->[$Index] ne $Param{Old}->[$Index];
}
}
# field has not been changed
return;
}
1;