1108 lines
32 KiB
Perl
1108 lines
32 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::Output::HTML::Layout::ITSMChange;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Kernel::Language qw(Translatable);
|
|
|
|
our $ObjectManagerDisabled = 1;
|
|
|
|
=head2 ITSMChangeBuildWorkOrderGraph()
|
|
|
|
returns a output string for WorkOrder graph
|
|
|
|
my $String = $LayoutObject->ITSMChangeBuildWorkOrderGraph(
|
|
Change => $ChangeRef,
|
|
WorkOrderObject => $WorkOrderObject,
|
|
DynamicFieldObject => $DynamicFieldObject,
|
|
BackendObject => $BackendObject,
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ITSMChangeBuildWorkOrderGraph {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get log object
|
|
my $LogObject = $Kernel::OM->Get('Kernel::System::Log');
|
|
|
|
# check for change
|
|
my $Change = $Param{Change};
|
|
if ( !$Change ) {
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => 'Need Change!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check workorder object
|
|
if ( !$Param{WorkOrderObject} ) {
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => 'Need WorkOrderObject!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# store workorder object locally
|
|
my $WorkOrderObject = $Param{WorkOrderObject};
|
|
|
|
# check if workorders are available
|
|
return if !$Change->{WorkOrderCount};
|
|
|
|
# extra check for ARRAY-ref
|
|
return if ref $Change->{WorkOrderIDs} ne 'ARRAY';
|
|
|
|
# hash for smallest time
|
|
my %Time;
|
|
|
|
TIMETYPE:
|
|
for my $TimeType (qw(Start End)) {
|
|
|
|
# actual time not set, so we can use planned
|
|
if ( !$Change->{"Actual${TimeType}Time"} ) {
|
|
|
|
# check if time is set
|
|
next TIMETYPE if !$Change->{"Planned${TimeType}Time"};
|
|
|
|
# translate to system-time/epoch
|
|
$Time{"${TimeType}Time"} = $Self->_TimeStamp2Epoch(
|
|
TimeStamp => $Change->{"Planned${TimeType}Time"},
|
|
);
|
|
|
|
# jump to next type
|
|
next TIMETYPE;
|
|
}
|
|
|
|
# translate planned time to timestamp for equation
|
|
$Time{"Planned${TimeType}Time"} = $Self->_TimeStamp2Epoch(
|
|
TimeStamp => $Change->{"Planned${TimeType}Time"},
|
|
);
|
|
|
|
# translate actual time to timestamp for equation
|
|
$Time{"Actual${TimeType}Time"} = $Self->_TimeStamp2Epoch(
|
|
TimeStamp => $Change->{"Actual${TimeType}Time"},
|
|
);
|
|
}
|
|
|
|
# set time attributes to empty string if not defined
|
|
for my $TimeAttribute (qw(PlannedStartTime PlannedEndTime ActualStartTime ActualEndTime)) {
|
|
$Time{$TimeAttribute} //= '';
|
|
}
|
|
|
|
# get smallest start time
|
|
if ( !$Time{StartTime} ) {
|
|
$Time{StartTime} = ( $Time{PlannedStartTime} lt $Time{ActualStartTime} )
|
|
? $Time{PlannedStartTime}
|
|
: $Time{ActualStartTime};
|
|
}
|
|
|
|
# get highest end time
|
|
if ( !$Time{EndTime} ) {
|
|
$Time{EndTime} = ( $Time{PlannedEndTime} gt $Time{ActualEndTime} )
|
|
? $Time{PlannedEndTime}
|
|
: $Time{ActualEndTime};
|
|
}
|
|
|
|
# Get current system unix time (epoch).
|
|
my $SystemTime = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime'
|
|
)->ToEpoch();
|
|
|
|
# check for real end of end time for scale and graph items
|
|
# only if ActualStartTime is set
|
|
if (
|
|
$Time{ActualStartTime}
|
|
&& !$Time{ActualEndTime}
|
|
&& ( $Time{EndTime} lt $SystemTime )
|
|
)
|
|
{
|
|
$Time{EndTime} = $SystemTime;
|
|
}
|
|
|
|
# calculate ticks for change
|
|
my $ChangeTicks = $Self->_ITSMChangeGetChangeTicks(
|
|
Start => $Time{StartTime},
|
|
End => $Time{EndTime},
|
|
);
|
|
|
|
# check for valid ticks
|
|
if ( !$ChangeTicks ) {
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => 'Unable to calculate time scale.',
|
|
);
|
|
}
|
|
|
|
# get workorders of change
|
|
my @WorkOrders;
|
|
WORKORDERID:
|
|
for my $WorkOrderID ( @{ $Change->{WorkOrderIDs} } ) {
|
|
my $WorkOrder = $WorkOrderObject->WorkOrderGet(
|
|
WorkOrderID => $WorkOrderID,
|
|
UserID => $Self->{UserID},
|
|
);
|
|
next WORKORDERID if !$WorkOrder;
|
|
|
|
push @WorkOrders, $WorkOrder;
|
|
}
|
|
|
|
# get config settings
|
|
my $ChangeZoomConfig = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::Frontend::AgentITSMChangeZoom');
|
|
|
|
# check config setting
|
|
if ( !$ChangeZoomConfig ) {
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => 'Need SysConfig settings for ITSMChange::Frontend::AgentITSMChangeZoom!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check graph config setting
|
|
if ( !$ChangeZoomConfig->{WorkOrderGraph} ) {
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => 'Need SysConfig settings for '
|
|
. 'ITSMChange::Frontend::AgentITSMChangeZoom###WorkOrderGraph!',
|
|
);
|
|
return;
|
|
}
|
|
|
|
# validity settings of graph settings
|
|
my %WorkOrderGraphCheck = (
|
|
TimeLineColor => '#[a-fA-F\d]{6}',
|
|
TimeLineWidth => '\d{1,2}',
|
|
undefined_planned_color => '#[a-fA-F\d]{6}',
|
|
undefined_actual_color => '#[a-fA-F\d]{6}',
|
|
);
|
|
|
|
# check validity of graph settings
|
|
my $WorkOrderGraphConfig = $ChangeZoomConfig->{WorkOrderGraph};
|
|
for my $GraphSetting ( sort keys %WorkOrderGraphCheck ) {
|
|
|
|
# check existense of config setting
|
|
if ( !$WorkOrderGraphConfig->{$GraphSetting} ) {
|
|
|
|
# display error and return
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => "Need SysConfig setting '$GraphSetting' in "
|
|
. "ITSMChange::Frontend::AgentITSMChangeZoom###WorkOrderGraph!",
|
|
);
|
|
return;
|
|
}
|
|
|
|
# check validity of config setting
|
|
if (
|
|
$WorkOrderGraphConfig->{$GraphSetting}
|
|
!~ m{ \A $WorkOrderGraphCheck{$GraphSetting} \z }xms
|
|
)
|
|
{
|
|
|
|
# display error and return
|
|
$LogObject->Log(
|
|
Priority => 'error',
|
|
Message => "SysConfig setting '$GraphSetting' is invalid in "
|
|
. "ITSMChange::Frontend::AgentITSMChangeZoom###WorkOrderGraph!",
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
# compute effecive label width
|
|
my $LabelWidth = 60;
|
|
if ( $ChangeZoomConfig->{WorkOrderState} && $ChangeZoomConfig->{WorkOrderTitle} ) {
|
|
$LabelWidth += 180;
|
|
}
|
|
elsif ( $ChangeZoomConfig->{WorkOrderState} ) {
|
|
$LabelWidth += 70;
|
|
}
|
|
elsif ( $ChangeZoomConfig->{WorkOrderTitle} ) {
|
|
$LabelWidth += 125;
|
|
}
|
|
|
|
# load graph skeleton
|
|
$Self->Block(
|
|
Name => 'WorkOrderGraph',
|
|
Data => {
|
|
LabelWidth => $LabelWidth,
|
|
LabelMargin => $LabelWidth + 2,
|
|
},
|
|
);
|
|
|
|
# create color definitions for all configured workorder types
|
|
my $WorkOrderTypes = $WorkOrderObject->WorkOrderTypeList(
|
|
UserID => $Self->{UserID},
|
|
) || [];
|
|
|
|
# create css definitions for workorder types
|
|
WORKORDERTYPE:
|
|
for my $WorkOrderType ( @{$WorkOrderTypes} ) {
|
|
|
|
# check workorder type
|
|
next WORKORDERTYPE if !$WorkOrderType;
|
|
next WORKORDERTYPE if !$WorkOrderType->{Value};
|
|
|
|
# get name of workorder type
|
|
my $WorkOrderTypeName = $WorkOrderType->{Value};
|
|
|
|
# check contents of name
|
|
next WORKORDERTYPE if !$WorkOrderTypeName;
|
|
|
|
for my $WorkOrderColor (qw( _planned _actual )) {
|
|
|
|
# get configured or fallback planned color for workorder
|
|
my $WorkOrderTypeColor = $WorkOrderGraphConfig->{"${WorkOrderTypeName}${WorkOrderColor}_color"};
|
|
|
|
# set default color if no color is found
|
|
$WorkOrderTypeColor ||= $WorkOrderGraphConfig->{"undefined${WorkOrderColor}_color"};
|
|
|
|
# check validity of workorder color
|
|
if ( $WorkOrderTypeColor !~ m{ \A # [A-Za-z\d]{6} \z }xms ) {
|
|
$WorkOrderTypeColor = $WorkOrderGraphConfig->{"undefined${WorkOrderColor}_color"};
|
|
}
|
|
|
|
# display css definitions for planned
|
|
$Self->Block(
|
|
Name => 'CSSWorkOrderType',
|
|
Data => {
|
|
WorkOrderTypeName => $WorkOrderTypeName . $WorkOrderColor,
|
|
WorkOrderTypeColor => $WorkOrderTypeColor,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# calculate time line parameter
|
|
my $TimeLine = $Self->_ITSMChangeGetTimeLine(
|
|
StartTime => $Time{StartTime},
|
|
EndTime => $Time{EndTime},
|
|
Ticks => $ChangeTicks,
|
|
);
|
|
|
|
if ( $TimeLine && defined $TimeLine->{TimeLineLeft} ) {
|
|
|
|
# calculate height of time line
|
|
my $WorkOrderHeight = 16;
|
|
my $ScaleMargin = 11;
|
|
$TimeLine->{TimeLineHeight} = ( ( scalar @WorkOrders ) * $WorkOrderHeight ) + $ScaleMargin;
|
|
|
|
# display css of timeline
|
|
$Self->Block(
|
|
Name => 'CSSTimeLine',
|
|
Data => {
|
|
%{$TimeLine},
|
|
%{$WorkOrderGraphConfig},
|
|
},
|
|
);
|
|
|
|
# display timeline container
|
|
$Self->Block(
|
|
Name => 'TimeLine',
|
|
Data => {},
|
|
);
|
|
}
|
|
|
|
# sort workorder ascending to WorkOrderNumber
|
|
@WorkOrders = sort { $a->{WorkOrderNumber} <=> $b->{WorkOrderNumber} } @WorkOrders;
|
|
|
|
# build graph of each workorder
|
|
WORKORDER:
|
|
for my $WorkOrder (@WorkOrders) {
|
|
next WORKORDER if !$WorkOrder;
|
|
|
|
$Self->_ITSMChangeGetWorkOrderGraph(
|
|
WorkOrder => $WorkOrder,
|
|
DynamicFieldObject => $Param{DynamicFieldObject},
|
|
BackendObject => $Param{BackendObject},
|
|
StartTime => $Time{StartTime},
|
|
EndTime => $Time{EndTime},
|
|
Ticks => $ChangeTicks,
|
|
);
|
|
}
|
|
|
|
# build scale of graph
|
|
$Self->_ITSMChangeGetChangeScale(
|
|
StartTime => $Time{StartTime},
|
|
EndTime => $Time{EndTime},
|
|
Ticks => $ChangeTicks,
|
|
LabelMargin => $LabelWidth + 2,
|
|
);
|
|
|
|
# render graph and return HTML with ITSMChange.dtl template
|
|
return $Self->Output(
|
|
TemplateFile => 'ITSMChange',
|
|
Data => {%Param},
|
|
);
|
|
}
|
|
|
|
=head2 ITSMChangeListShow()
|
|
|
|
Returns a list of changes as C<sortable> list with pagination.
|
|
|
|
This function is similar to L<Kernel::Output::HTML::LayoutTicket::TicketListShow()>
|
|
in F<Kernel/Output/HTML/LayoutTicket.pm>.
|
|
|
|
my $Output = $LayoutObject->ITSMChangeListShow(
|
|
ChangeIDs => $ChangeIDsRef, # total list of change ids, that can be listed
|
|
Total => scalar @{ $ChangeIDsRef }, # total number of list items, changes in this case
|
|
View => $Self->{View}, # optional, the default value is 'Small'
|
|
Filter => 'All',
|
|
Filters => \%NavBarFilter,
|
|
FilterLink => $LinkFilter,
|
|
TitleName => 'Overview: Changes',
|
|
TitleValue => $Self->{Filter},
|
|
Env => $Self,
|
|
LinkPage => $LinkPage,
|
|
LinkSort => $LinkSort,
|
|
Frontend => 'Agent', # optional (Agent|Customer), default: Agent, indicates from which frontend this function was called
|
|
);
|
|
|
|
=cut
|
|
|
|
sub ITSMChangeListShow {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# take object ref to local, remove it from %Param (prevent memory leak)
|
|
my $Env = delete $Param{Env};
|
|
|
|
# lookup latest used view mode
|
|
if ( !$Param{View} && $Self->{ 'UserITSMChangeOverview' . $Env->{Action} } ) {
|
|
$Param{View} = $Self->{ 'UserITSMChangeOverview' . $Env->{Action} };
|
|
}
|
|
|
|
# set frontend
|
|
my $Frontend = $Param{Frontend} || 'Agent';
|
|
|
|
# set defaut view mode to 'small'
|
|
my $View = $Param{View} || 'Small';
|
|
|
|
# store latest view mode
|
|
$Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
|
|
SessionID => $Self->{SessionID},
|
|
Key => 'UserITSMChangeOverview' . $Env->{Action},
|
|
Value => $View,
|
|
);
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
|
|
# get backend from config
|
|
my $Backends = $ConfigObject->Get('ITSMChange::Frontend::Overview');
|
|
if ( !$Backends ) {
|
|
return $LayoutObject->FatalError(
|
|
Message => $LayoutObject->{LanguageObject}->Translate(
|
|
'Need config option %s!',
|
|
'ITSMChange::Frontend::Overview',
|
|
),
|
|
Comment => Translatable('Please contact the administrator.'),
|
|
);
|
|
}
|
|
|
|
# check for hash-ref
|
|
if ( ref $Backends ne 'HASH' ) {
|
|
return $LayoutObject->FatalError(
|
|
Message => $LayoutObject->{LanguageObject}->Translate(
|
|
'Config option %s needs to be a HASH ref!',
|
|
'ITSMChange::Frontend::Overview',
|
|
),
|
|
Comment => Translatable('Please contact the administrator.'),
|
|
);
|
|
}
|
|
|
|
# check for config key
|
|
if ( !$Backends->{$View} ) {
|
|
return $LayoutObject->FatalError(
|
|
Message => $LayoutObject->{LanguageObject}->Translate( 'No config option found for the view "%s"!', $View ),
|
|
Comment => Translatable('Please contact the administrator.'),
|
|
);
|
|
}
|
|
|
|
# nav bar
|
|
my $StartHit = $Kernel::OM->Get('Kernel::System::Web::Request')->GetParam(
|
|
Param => 'StartHit',
|
|
) || 1;
|
|
|
|
# get personal page shown count
|
|
my $PageShownPreferencesKey = 'UserChangeOverview' . $View . 'PageShown';
|
|
my $PageShown = $Self->{$PageShownPreferencesKey} || 10;
|
|
my $Group = 'ChangeOverview' . $View . 'PageShown';
|
|
|
|
# check start option, if higher then elements available, set
|
|
# it to the last overview page (Thanks to Stefan Schmidt!)
|
|
if ( $StartHit > $Param{Total} ) {
|
|
my $Pages = int( ( $Param{Total} / $PageShown ) + 0.99999 );
|
|
$StartHit = ( ( $Pages - 1 ) * $PageShown ) + 1;
|
|
}
|
|
|
|
# get data selection
|
|
my %Data;
|
|
my $Config = $ConfigObject->Get('PreferencesGroups');
|
|
if ( $Config && $Config->{$Group} && $Config->{$Group}->{Data} ) {
|
|
%Data = %{ $Config->{$Group}->{Data} };
|
|
}
|
|
|
|
# set page limit and build page nav
|
|
my $Limit = $Param{Limit} || 20_000;
|
|
my %PageNav = $LayoutObject->PageNavBar(
|
|
Limit => $Limit,
|
|
StartHit => $StartHit,
|
|
PageShown => $PageShown,
|
|
AllHits => $Param{Total} || 0,
|
|
Action => 'Action=' . $LayoutObject->{Action},
|
|
Link => $Param{LinkPage},
|
|
);
|
|
|
|
# build shown ticket a page
|
|
$Param{RequestedURL} = $Param{RequestedURL} || "Action=$Self->{Action}";
|
|
$Param{Group} = $Group;
|
|
$Param{PreferencesKey} = $PageShownPreferencesKey;
|
|
$Param{PageShownString} = $Self->BuildSelection(
|
|
Name => $PageShownPreferencesKey,
|
|
SelectedID => $PageShown,
|
|
Data => \%Data,
|
|
Translation => 0,
|
|
Sort => 'NumericValue',
|
|
Class => 'Modernize',
|
|
);
|
|
|
|
# build navbar content
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBar',
|
|
Data => \%Param,
|
|
);
|
|
|
|
# back link
|
|
if ( $Param{LinkBack} ) {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarPageBack',
|
|
Data => \%Param,
|
|
);
|
|
|
|
$LayoutObject->AddJSData(
|
|
Key => 'ITSMChangeMgmtChangeSearch',
|
|
Value => {
|
|
Profile => $Param{Profile},
|
|
},
|
|
);
|
|
}
|
|
|
|
# get filters
|
|
if ( $Param{Filters} ) {
|
|
|
|
# get given filters
|
|
my @NavBarFilters;
|
|
for my $Prio ( sort keys %{ $Param{Filters} } ) {
|
|
push @NavBarFilters, $Param{Filters}->{$Prio};
|
|
}
|
|
|
|
# build filter content
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarFilter',
|
|
Data => {
|
|
%Param,
|
|
},
|
|
);
|
|
|
|
# loop over filters
|
|
my $Count = 0;
|
|
for my $Filter (@NavBarFilters) {
|
|
|
|
# increment filter count and build filter item
|
|
$Count++;
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarFilterItem',
|
|
Data => {
|
|
%Param,
|
|
%{$Filter},
|
|
},
|
|
);
|
|
|
|
# filter is selected
|
|
if ( $Filter->{Filter} eq $Param{Filter} ) {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarFilterItemSelected',
|
|
Data => {
|
|
%Param,
|
|
%{$Filter},
|
|
},
|
|
);
|
|
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarFilterItemSelectedNot',
|
|
Data => {
|
|
%Param,
|
|
%{$Filter},
|
|
},
|
|
);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
# loop over configured backends
|
|
for my $Backend ( sort keys %{$Backends} ) {
|
|
|
|
# build navbar view mode
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarViewMode',
|
|
Data => {
|
|
%Param,
|
|
%{ $Backends->{$Backend} },
|
|
Filter => $Param{Filter},
|
|
View => $Backend,
|
|
},
|
|
);
|
|
|
|
# current view is configured in backend
|
|
if ( $View eq $Backend ) {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarViewModeSelected',
|
|
Data => {
|
|
%Param,
|
|
%{ $Backends->{$Backend} },
|
|
Filter => $Param{Filter},
|
|
View => $Backend,
|
|
},
|
|
);
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarViewModeNotSelected',
|
|
Data => {
|
|
%Param,
|
|
%{ $Backends->{$Backend} },
|
|
Filter => $Param{Filter},
|
|
View => $Backend,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# check if page nav is available
|
|
if (%PageNav) {
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBarPageNavBar',
|
|
Data => \%PageNav,
|
|
);
|
|
|
|
# don't show context settings in AJAX case (e. g. in customer ticket history),
|
|
# because the submit with page reload will not work there
|
|
if ( !$Param{AJAX} ) {
|
|
$LayoutObject->Block(
|
|
Name => 'ContextSettings',
|
|
Data => {
|
|
%PageNav,
|
|
%Param,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# build html content
|
|
my $OutputNavBar = $LayoutObject->Output(
|
|
TemplateFile => 'AgentITSMChangeOverviewNavBar',
|
|
Data => {%Param},
|
|
);
|
|
|
|
# create output
|
|
my $OutputRaw = '';
|
|
if ( !$Param{Output} ) {
|
|
$LayoutObject->Print(
|
|
Output => \$OutputNavBar,
|
|
);
|
|
}
|
|
else {
|
|
$OutputRaw .= $OutputNavBar;
|
|
}
|
|
|
|
# load module
|
|
if ( !$Kernel::OM->Get('Kernel::System::Main')->Require( $Backends->{$View}->{Module} ) ) {
|
|
return $LayoutObject->FatalError();
|
|
}
|
|
|
|
# check for backend object
|
|
my $Object = $Backends->{$View}->{Module}->new( %{$Env} );
|
|
return if !$Object;
|
|
|
|
# run module
|
|
my $Output = $Object->Run(
|
|
%Param,
|
|
Limit => $Limit,
|
|
StartHit => $StartHit,
|
|
PageShown => $PageShown,
|
|
AllHits => $Param{Total} || 0,
|
|
Frontend => $Frontend,
|
|
);
|
|
|
|
# create output
|
|
if ( !$Param{Output} ) {
|
|
$LayoutObject->Print(
|
|
Output => \$Output,
|
|
);
|
|
}
|
|
else {
|
|
$OutputRaw .= $Output;
|
|
}
|
|
|
|
# create overview nav bar
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewNavBar',
|
|
Data => {%Param},
|
|
);
|
|
|
|
# return content if available
|
|
return $OutputRaw;
|
|
}
|
|
|
|
=head1 PRIVATE INTERFACE
|
|
|
|
=head2 _ITSMChangeGetChangeTicks()
|
|
|
|
a helper method for the C<workorder> graph of a change
|
|
|
|
=cut
|
|
|
|
sub _ITSMChangeGetChangeTicks {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check for start and end
|
|
return if !$Param{Start} || !$Param{End};
|
|
|
|
# make sure we got integers
|
|
return if $Param{Start} !~ m{ \A \d+ \z }xms;
|
|
return if $Param{End} !~ m{ \A \d+ \z }xms;
|
|
|
|
# calculate time span in sec
|
|
my $Ticks = $Param{End} - $Param{Start};
|
|
|
|
# check for computing error
|
|
return if $Ticks <= 0;
|
|
|
|
# get seconds per percent and round down
|
|
$Ticks = sprintf( "%.f", $Ticks / 100 );
|
|
|
|
return $Ticks;
|
|
}
|
|
|
|
=head2 _ITSMChangeGetChangeScale()
|
|
|
|
a helper method for the C<workorder> graph of a change
|
|
|
|
=cut
|
|
|
|
sub _ITSMChangeGetChangeScale {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check for start time
|
|
return if !$Param{StartTime};
|
|
|
|
# check for start time is an integer value
|
|
return if $Param{StartTime} !~ m{ \A \d+ \z }xms;
|
|
|
|
# add start and end time and calculate scale naming
|
|
my %ScaleName = (
|
|
StartTime => $Param{StartTime},
|
|
EndTime => $Param{EndTime},
|
|
Scale15 => ( $Param{StartTime} + 20 * $Param{Ticks} ),
|
|
Scale35 => ( $Param{StartTime} + 40 * $Param{Ticks} ),
|
|
Scale55 => ( $Param{StartTime} + 60 * $Param{Ticks} ),
|
|
Scale75 => ( $Param{StartTime} + 80 * $Param{Ticks} ),
|
|
);
|
|
|
|
# translate timestamps in date format
|
|
map {
|
|
$ScaleName{$_} = $Self->_Epoch2TimeStamp(
|
|
Epoch => $ScaleName{$_},
|
|
)
|
|
} keys %ScaleName;
|
|
|
|
# create scale block
|
|
$Self->Block(
|
|
Name => 'Scale',
|
|
Data => {
|
|
%ScaleName,
|
|
LabelMargin => $Param{LabelMargin},
|
|
},
|
|
);
|
|
|
|
INTERVAL:
|
|
for my $Interval ( sort keys %ScaleName ) {
|
|
|
|
# do not display scale if translating failed
|
|
next INTERVAL if !$ScaleName{$Interval};
|
|
|
|
# do not display start or end
|
|
next INTERVAL if $Interval =~ m{ \A ( Start | End ) Time \z }xms;
|
|
|
|
# build scale label block
|
|
$Self->Block(
|
|
Name => 'ScaleLabel',
|
|
Data => {
|
|
ScaleLabel => $ScaleName{$Interval},
|
|
ScaleClass => $Interval,
|
|
},
|
|
);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 _ITSMChangeGetWorkOrderGraph()
|
|
|
|
a helper method for the C<workorder> graph of a change
|
|
|
|
=cut
|
|
|
|
sub _ITSMChangeGetWorkOrderGraph {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check for workorder
|
|
return if !$Param{WorkOrder};
|
|
|
|
# extract workorder
|
|
my $WorkOrder = $Param{WorkOrder};
|
|
|
|
# save orig workorder for workorder information
|
|
my %WorkOrderInformation = %{$WorkOrder};
|
|
|
|
# translate workorder type
|
|
$WorkOrder->{TranslatedWorkOrderType} = $Self->{LanguageObject}->Translate( $WorkOrder->{WorkOrderType} );
|
|
|
|
# build label for link in graph
|
|
$WorkOrder->{WorkOrderLabel} = $Self->{LanguageObject}->Translate(
|
|
'Title: %s | Type: %s',
|
|
$WorkOrder->{WorkOrderTitle},
|
|
$WorkOrder->{TranslatedWorkOrderType},
|
|
);
|
|
|
|
# create workorder item
|
|
$Self->Block(
|
|
Name => 'WorkOrderItem',
|
|
Data => {
|
|
%{$WorkOrder},
|
|
},
|
|
);
|
|
|
|
# get config settings
|
|
my $ChangeZoomConfig = $Kernel::OM->Get('Kernel::Config')->Get('ITSMChange::Frontend::AgentITSMChangeZoom');
|
|
|
|
# add workorder state
|
|
if ( $ChangeZoomConfig->{WorkOrderState} ) {
|
|
$Self->Block(
|
|
Name => 'WorkOrderItemState',
|
|
Data => {
|
|
%{$WorkOrder},
|
|
},
|
|
);
|
|
}
|
|
|
|
# add workorder title
|
|
if ( $ChangeZoomConfig->{WorkOrderTitle} ) {
|
|
$Self->Block(
|
|
Name => 'WorkOrderItemTitle',
|
|
Data => {
|
|
%{$WorkOrder},
|
|
},
|
|
);
|
|
}
|
|
|
|
# check if ticks are calculated
|
|
return if !$Param{Ticks};
|
|
|
|
# set planned if no actual time is set
|
|
if ( !$WorkOrder->{ActualStartTime} ) {
|
|
$WorkOrder->{ActualStartTime} = $WorkOrder->{PlannedStartTime};
|
|
$WorkOrder->{ActualEndTime} = $WorkOrder->{PlannedEndTime};
|
|
}
|
|
|
|
# set current time if no actual end time is set
|
|
if ( $WorkOrder->{ActualStartTime} && !$WorkOrder->{ActualEndTime} ) {
|
|
$WorkOrder->{ActualEndTime} = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
)->ToString();
|
|
}
|
|
|
|
# set nice display of undef actual times
|
|
for my $TimeType (qw(ActualStartTime ActualEndTime)) {
|
|
if ( !$WorkOrderInformation{$TimeType} ) {
|
|
$WorkOrderInformation{"Empty${TimeType}"} = '-';
|
|
}
|
|
}
|
|
|
|
# hash for time values
|
|
my %Time;
|
|
|
|
for my $TimeType (qw(PlannedStartTime PlannedEndTime ActualStartTime ActualEndTime)) {
|
|
|
|
# translate time
|
|
$Time{$TimeType} = $Self->_TimeStamp2Epoch(
|
|
TimeStamp => $WorkOrder->{$TimeType},
|
|
);
|
|
}
|
|
|
|
# determine length of workorder
|
|
my %TickValue;
|
|
|
|
for my $TimeType (qw( Planned Actual )) {
|
|
|
|
# get values for padding span
|
|
my $StartPadding = sprintf(
|
|
"%.1f",
|
|
( $Time{"${TimeType}StartTime"} - $Param{StartTime} ) / $Param{Ticks}
|
|
);
|
|
$StartPadding = ( $StartPadding <= 0 ) ? 0 : $StartPadding;
|
|
$StartPadding = ( $StartPadding >= 100 ) ? 99.9 : $StartPadding;
|
|
$TickValue{"${TimeType}Padding"} = $StartPadding;
|
|
|
|
# get values for trailing span
|
|
my $EndTrailing = sprintf( "%.1f", ( $Param{EndTime} - $Time{"${TimeType}EndTime"} ) / $Param{Ticks} );
|
|
$EndTrailing = ( $EndTrailing <= 0 ) ? 0 : $EndTrailing;
|
|
$EndTrailing = ( $EndTrailing >= 100 ) ? 99.9 : $EndTrailing;
|
|
$TickValue{"${TimeType}Trailing"} = $EndTrailing;
|
|
|
|
# get values for display span
|
|
my $TimeTicks = 100 - ( $TickValue{"${TimeType}Padding"} + $TickValue{"${TimeType}Trailing"} );
|
|
$TimeTicks = ( $TimeTicks <= 0 ) ? 0.1 : $TimeTicks;
|
|
$TimeTicks = ( $TimeTicks >= 100 ) ? 99.9 : $TimeTicks;
|
|
$TickValue{"${TimeType}Ticks"} = sprintf( "%.1f", $TimeTicks );
|
|
}
|
|
|
|
# set workorder as inactive if it is not started jet
|
|
if ( !$WorkOrderInformation{ActualStartTime} ) {
|
|
$WorkOrderInformation{WorkOrderOpacity} = 'WorkorderInactive';
|
|
}
|
|
|
|
# set workorder agent
|
|
if ( $WorkOrderInformation{WorkOrderAgentID} ) {
|
|
my %WorkOrderAgentData = $Kernel::OM->Get('Kernel::System::User')->GetUserData(
|
|
UserID => $WorkOrderInformation{WorkOrderAgentID},
|
|
Cached => 1,
|
|
);
|
|
|
|
if (%WorkOrderAgentData) {
|
|
|
|
# get WorkOrderAgent information
|
|
for my $Postfix (qw(UserLogin UserFullname)) {
|
|
$WorkOrderInformation{"WorkOrderAgent$Postfix"} = $WorkOrderAgentData{$Postfix}
|
|
|| '';
|
|
}
|
|
}
|
|
}
|
|
|
|
# set the graph direction (LTR: left, RTL: right)
|
|
if ( $Self->{TextDirection} && $Self->{TextDirection} eq 'rtl' ) {
|
|
$WorkOrderInformation{"GraphDirection"} = 'right';
|
|
}
|
|
else {
|
|
$WorkOrderInformation{"GraphDirection"} = 'left';
|
|
}
|
|
|
|
# create graph of workorder item
|
|
$Self->Block(
|
|
Name => 'WorkOrderItemGraph',
|
|
Data => {
|
|
%WorkOrderInformation,
|
|
%TickValue,
|
|
},
|
|
);
|
|
|
|
# get the workorder attribute names that should be shown in the tooltip
|
|
my %TooltipAttributes = %{ $ChangeZoomConfig->{'Tooltip::WorkOrderAttributes'} };
|
|
my @ShowAttributes = grep { $TooltipAttributes{$_} } keys %TooltipAttributes;
|
|
|
|
# build attribut blocks
|
|
if (@ShowAttributes) {
|
|
|
|
ATTRIBUTE:
|
|
for my $Attribute ( sort @ShowAttributes ) {
|
|
|
|
# special handling for workorder agent
|
|
if ( $Attribute eq 'WorkOrderAgent' ) {
|
|
|
|
$Self->Block(
|
|
Name => 'WorkOrderAgentBlock',
|
|
Data => {
|
|
%WorkOrderInformation,
|
|
},
|
|
);
|
|
|
|
# check the last thing: UserLogin
|
|
if ( $WorkOrderInformation{WorkOrderAgentUserLogin} ) {
|
|
$Self->Block(
|
|
Name => 'WorkOrderAgent',
|
|
Data => {
|
|
%WorkOrderInformation,
|
|
},
|
|
);
|
|
}
|
|
else {
|
|
$Self->Block(
|
|
Name => 'EmptyWorkOrderAgent',
|
|
Data => {
|
|
%WorkOrderInformation,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# handle workorder dynamic fields
|
|
elsif ( $Attribute =~ m{ \A DynamicField_ (.+) }xms ) {
|
|
|
|
my $DynamicFieldName = $1;
|
|
|
|
# only if the workorder dynamic field contains something
|
|
next ATTRIBUTE if !$WorkOrderInformation{ 'DynamicField_' . $DynamicFieldName };
|
|
|
|
# get config for this dynamic field
|
|
my $DynamicFieldConfig = $Param{DynamicFieldObject}->DynamicFieldGet(
|
|
Name => $DynamicFieldName,
|
|
);
|
|
|
|
next ATTRIBUTE if !$DynamicFieldConfig;
|
|
|
|
# get print string for this dynamic field
|
|
my $ValueStrg = $Param{BackendObject}->DisplayValueRender(
|
|
DynamicFieldConfig => $DynamicFieldConfig,
|
|
Value => $WorkOrderInformation{ 'DynamicField_' . $DynamicFieldName },
|
|
ValueMaxChars => 50,
|
|
LayoutObject => $Self,
|
|
);
|
|
|
|
$Self->Block(
|
|
Name => 'DynamicField',
|
|
Data => {
|
|
Label => $DynamicFieldConfig->{Label},
|
|
Value => $ValueStrg->{Value},
|
|
},
|
|
);
|
|
}
|
|
|
|
# all other attributes
|
|
else {
|
|
$Self->Block(
|
|
Name => $Attribute,
|
|
Data => {
|
|
%WorkOrderInformation,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
=head2 _ITSMChangeGetTimeLine()
|
|
|
|
a helper method for the C<workorder> graph of a change
|
|
|
|
=cut
|
|
|
|
sub _ITSMChangeGetTimeLine {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# check for start time
|
|
return if !$Param{StartTime};
|
|
|
|
# check for start time is an integer value
|
|
return if $Param{StartTime} !~ m{ \A \d+ \z }xms;
|
|
|
|
# check for end time
|
|
return if !$Param{EndTime};
|
|
|
|
# check for end time is an integer value
|
|
return if $Param{EndTime} !~ m{ \A \d+ \z }xms;
|
|
|
|
# check for ticks
|
|
return if !$Param{Ticks};
|
|
|
|
# check for ticks is an integer value
|
|
return if $Param{Ticks} !~ m{ \A \d+ \z }xms;
|
|
|
|
# get current system time
|
|
my $CurrentTime = $Kernel::OM->Create('Kernel::System::DateTime')->ToEpoch();
|
|
|
|
# check for system time
|
|
return if !$CurrentTime;
|
|
|
|
# check if current time is in change time interval
|
|
return if $CurrentTime < $Param{StartTime};
|
|
return if $CurrentTime > $Param{EndTime};
|
|
|
|
# time line data
|
|
my %TimeLine;
|
|
|
|
# calculate percent of timeline
|
|
my $RelativeEnd = $Param{EndTime} - $Param{StartTime};
|
|
my $RelativeStart = $CurrentTime - $Param{StartTime};
|
|
|
|
# get timeline indent with 1 digit after decimal point
|
|
$TimeLine{TimeLineLeft} = sprintf( "%.1f", ( $RelativeStart / $RelativeEnd ) * 100 );
|
|
|
|
# verify percent values
|
|
if ( $TimeLine{TimeLineLeft} <= 0 ) {
|
|
$TimeLine{TimeLineLeft} = 0;
|
|
}
|
|
if ( $TimeLine{TimeLineLeft} >= 100 ) {
|
|
$TimeLine{TimeLineLeft} = 99.9;
|
|
}
|
|
|
|
return \%TimeLine;
|
|
}
|
|
|
|
=head2 _TimeStamp2Epoch()
|
|
|
|
Convert a timestamp to a epoch (unix time).
|
|
|
|
=cut
|
|
|
|
sub _TimeStamp2Epoch {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
String => $Param{TimeStamp},
|
|
},
|
|
);
|
|
|
|
return $DateTimeObject->ToEpoch() if $DateTimeObject;
|
|
return;
|
|
}
|
|
|
|
=head2 _Epoch2TimeStamp()
|
|
|
|
Convert a epoch (unix time) to a timestamp (C<yyyy-mm-dd hh:mm:ss>).
|
|
|
|
=cut
|
|
|
|
sub _Epoch2TimeStamp {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Epoch => $Param{Epoch},
|
|
},
|
|
);
|
|
|
|
return $DateTimeObject->ToString() if $DateTimeObject;
|
|
return;
|
|
}
|
|
|
|
1;
|