# -- # 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::Modules::AgentITSMChangePrint; use strict; use warnings; use List::Util qw(max); use Kernel::Language qw(Translatable); use Kernel::System::VariableCheck qw(:all); our $ObjectManagerDisabled = 1; sub new { my ( $Type, %Param ) = @_; # allocate new hash for object my $Self = {%Param}; bless( $Self, $Type ); return $Self; } sub Run { my ( $Self, %Param ) = @_; # get param object my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request'); # Find out whether a change or a workorder should be printed. # A workorder is to be printed when the WorkOrderID is passed. # Otherwise a change should be printed. my $WorkOrderID = $ParamObject->GetParam( Param => 'WorkOrderID' ); my $PrintWorkOrder = $WorkOrderID ? 1 : 0; my $PrintChange = !$WorkOrderID; my $WorkOrder = {}; my $ChangeID; # get needed objects my $ChangeObject = $Kernel::OM->Get('Kernel::System::ITSMChange'); my $WorkOrderObject = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder'); my $ConfigObject = $Kernel::OM->Get('Kernel::Config'); my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # get config of frontend module $Self->{Config} = $ConfigObject->Get("ITSMChange::Frontend::$Self->{Action}"); if ($PrintWorkOrder) { # check permission on the workorder my $Access = $WorkOrderObject->Permission( Type => $Self->{Config}->{Permission}, Action => $Self->{Action}, WorkOrderID => $WorkOrderID, UserID => $Self->{UserID}, ); # error screen if ( !$Access ) { return $LayoutObject->NoPermission( Message => $LayoutObject->{LanguageObject} ->Translate( 'You need %s permissions!', $Self->{Config}->{Permission} ), WithHeader => 'yes', ); } # get workorder information $WorkOrder = $WorkOrderObject->WorkOrderGet( WorkOrderID => $WorkOrderID, UserID => $Self->{UserID}, ); # check error if ( !$WorkOrder ) { return $LayoutObject->ErrorScreen( Message => $LayoutObject->{LanguageObject}->Translate( 'WorkOrder "%s" not found in database!', $WorkOrderID ), Comment => Translatable('Please contact the administrator.'), ); } # infer the change id from the workorder $ChangeID = $WorkOrder->{ChangeID}; if ( !$ChangeID ) { # error page return $LayoutObject->ErrorScreen( Message => Translatable('Can\'t create output, as the workorder is not attached to a change!'), Comment => Translatable('Please contact the administrator.'), ); } } else { # the change id is required, as we have no workorder id $ChangeID = $ParamObject->GetParam( Param => 'ChangeID' ); if ( !$ChangeID ) { # error page return $LayoutObject->ErrorScreen( Message => Translatable('Can\'t create output, as no ChangeID is given!'), Comment => Translatable('Please contact the administrator.'), ); } # check permission on the change my $Access = $ChangeObject->Permission( Type => $Self->{Config}->{Permission}, Action => $Self->{Action}, ChangeID => $ChangeID, UserID => $Self->{UserID}, ); # error screen if ( !$Access ) { return $LayoutObject->NoPermission( Message => $LayoutObject->{LanguageObject} ->Translate( 'You need %s permissions!', $Self->{Config}->{Permission} ), WithHeader => 'yes', ); } } # get change information my $Change = $ChangeObject->ChangeGet( ChangeID => $ChangeID, UserID => $Self->{UserID}, ); # check error if ( !$Change ) { return $LayoutObject->ErrorScreen( Message => $LayoutObject->{LanguageObject}->Translate( 'Change "%s" not found in database!', $ChangeID ), Comment => Translatable('Please contact the administrator.'), ); } # some init for PDF-Output # page controls the PDF-generation $Self->{Page} = {}; my $Page = $Self->{Page}; # get maximum number of pages $Page->{MaxPages} = $ConfigObject->Get('PDF::MaxPages'); if ( !$Page->{MaxPages} || $Page->{MaxPages} < 1 || $Page->{MaxPages} > 1000 ) { $Page->{MaxPages} = 100; } # page layout settings $Page->{MarginTop} = 30; $Page->{MarginRight} = 40; $Page->{MarginBottom} = 40; $Page->{MarginLeft} = 40; # the second item in the page title is the area in the product 'ITSM Change Management' my $HeaderArea = $PrintChange ? 'ITSM Change' : 'ITSM Workorder'; $HeaderArea = $LayoutObject->{LanguageObject}->Translate($HeaderArea); # the last item in the page title is either the change number of the full workorder number my $HeaderValue = $PrintChange ? $Change->{ChangeNumber} : join( '-', $Change->{ChangeNumber}, $WorkOrder->{WorkOrderNumber} ); # start the document my $Output = $Self->_StartDocument( HeaderArea => $HeaderArea, HeaderValue => $HeaderValue, ); # get link object my $LinkObject = $Kernel::OM->Get('Kernel::System::LinkObject'); # the link types are needed for showing the linked objects my %LinkTypeList = $LinkObject->TypeList( UserID => $Self->{UserID}, ); # print the change specific stuff if ($PrintChange) { # start the first page $Output .= $Self->_OutputHeadline( HeaderArea => $HeaderArea, HeaderValue => $HeaderValue, Title => $Change->{ChangeTitle} || Translatable('unknown change title'), TemplatePrefix => 'Change', ); # output change info $Output .= $Self->_OutputChangeInfo( Change => $Change, PrintWorkOrder => $PrintWorkOrder, ); # output change description and justification # the plain content will be displayed for my $Attribute (qw(Description Justification)) { $Output .= $Self->_OutputLongText( PrintChange => $PrintChange, PrintWorkOrder => $PrintWorkOrder, Title => $LayoutObject->{LanguageObject}->Translate($Attribute), LongText => $Change->{ $Attribute . 'Plain' }, ); } # get linked objects which are directly linked with this change object my $LinkListWithData = $LinkObject->LinkListWithData( Object => 'ITSMChange', Key => $ChangeID, State => 'Valid', UserID => $Self->{UserID}, ); # get the combined linked objects from all workorders of this change my $LinkListWithDataCombinedWorkOrders = {}; for my $WorkOrderID ( @{ $Change->{WorkOrderIDs} } ) { # get linked objects of this workorder my $LinkListWithDataWorkOrder = $LinkObject->LinkListWithData( Object => 'ITSMWorkOrder', Key => $WorkOrderID, State => 'Valid', UserID => $Self->{UserID}, ); OBJECT: for my $Object ( sort keys %{$LinkListWithDataWorkOrder} ) { # only show linked services and config items of workorder if ( $Object ne 'Service' && $Object ne 'ITSMConfigItem' ) { next OBJECT; } LINKTYPE: for my $LinkType ( sort keys %{ $LinkListWithDataWorkOrder->{$Object} } ) { DIRECTION: for my $Direction ( sort keys %{ $LinkListWithDataWorkOrder->{$Object}->{$LinkType} } ) { ID: for my $ID ( sort keys %{ $LinkListWithDataWorkOrder->{$Object}->{$LinkType}->{$Direction} } ) { # combine the linked object data from all workorders $LinkListWithDataCombinedWorkOrders->{$Object}->{$LinkType} ->{$Direction}->{$ID} = $LinkListWithDataWorkOrder->{$Object}->{$LinkType}->{$Direction} ->{$ID}; } } } } } # add combined linked objects from workorder to linked objects from change object $LinkListWithData = { %{$LinkListWithData}, %{$LinkListWithDataCombinedWorkOrders}, }; # get the link data if ( $LinkListWithData && ref $LinkListWithData eq 'HASH' && %{$LinkListWithData} ) { my %LinkData = $LayoutObject->LinkObjectTableCreate( LinkListWithData => $LinkListWithData, ViewMode => 'SimpleRaw', ); $Output .= $Self->_OutputLinkedObjects( PrintChange => $PrintChange, PrintWorkOrder => $PrintWorkOrder, LinkData => \%LinkData, LinkTypeList => \%LinkTypeList, ); } # output an overview over workorders my @WorkOrderOverview; for my $WorkOrderID ( @{ $Change->{WorkOrderIDs} } ) { # get workorder info my $WorkOrder = $WorkOrderObject->WorkOrderGet( WorkOrderID => $WorkOrderID, UserID => $Self->{UserID}, ); # check error if ( !$WorkOrder ) { return $LayoutObject->ErrorScreen( Message => $LayoutObject->{LanguageObject} ->Translate( 'WorkOrder "%s" not found in database!', $WorkOrderID ), Comment => Translatable('Please contact the administrator.'), ); } push @WorkOrderOverview, [ $WorkOrder->{WorkOrderNumber}, $WorkOrder->{WorkOrderTitle}, $WorkOrder->{WorkOrderState}, $WorkOrder->{PlannedStartTime}, $WorkOrder->{PlannedEndTime}, $WorkOrder->{ActualStartTime}, $WorkOrder->{ActualEndTime}, ]; } $Output .= $Self->_OutputWorkOrderOverview( WorkOrderOverview => \@WorkOrderOverview, ); } # output either a single workorder or all workorders of a change my @WorkOrderIDs = $PrintChange ? @{ $Change->{WorkOrderIDs} || [] } : ($WorkOrderID); for my $WorkOrderID (@WorkOrderIDs) { # get workorder info my $WorkOrder = $WorkOrderObject->WorkOrderGet( WorkOrderID => $WorkOrderID, UserID => $Self->{UserID}, ); # check error if ( !$WorkOrder ) { return $LayoutObject->ErrorScreen( Message => $LayoutObject->{LanguageObject}->Translate( 'WorkOrder "%s" not found in database!', $WorkOrderID, ), Comment => Translatable('Please contact the administrator.'), ); } # start a new page for every workorder my $HeaderArea = $LayoutObject->{LanguageObject}->Translate('ITSM Workorder'); my $HeaderValue = join '-', $Change->{ChangeNumber}, $WorkOrder->{ Translatable('WorkOrderNumber') }; $Output .= $Self->_OutputHeadline( HeaderArea => $HeaderArea, HeaderValue => $HeaderValue, Title => $WorkOrder->{ Translatable('WorkOrderTitle') } || Translatable('unknown workorder title'), TemplatePrefix => 'WorkOrder', ); $Output .= $Self->_OutputWorkOrderInfo( Change => $Change, WorkOrder => $WorkOrder, ); # output workorder instruction and report # The plain content will be displayed for my $Attribute (qw(Instruction Report)) { $Output .= $Self->_OutputLongText( PrintChange => 0, PrintWorkOrder => 1, Title => $LayoutObject->{LanguageObject}->Translate($Attribute), LongText => $WorkOrder->{ $Attribute . 'Plain' }, ); } # get linked objects my $LinkListWithData = $LinkObject->LinkListWithData( Object => 'ITSMWorkOrder', Key => $WorkOrderID, State => 'Valid', UserID => $Self->{UserID}, ); # get the link data if ( $LinkListWithData && ref $LinkListWithData eq 'HASH' && %{$LinkListWithData} ) { my %LinkData = $LayoutObject->LinkObjectTableCreate( LinkListWithData => $LinkListWithData, ViewMode => 'SimpleRaw', ); $Output .= $Self->_OutputLinkedObjects( PrintChange => 0, PrintWorkOrder => 1, LinkData => \%LinkData, LinkTypeList => \%LinkTypeList, ); } } # generate PDF output # get time object # generate a filename my $CurSysDTObject = $Kernel::OM->Create('Kernel::System::DateTime'); my @Filename; if ($PrintChange) { push @Filename, 'change', $Change->{ChangeNumber}; } else { push @Filename, 'workorder', $Change->{ChangeNumber} . '-' . $WorkOrder->{WorkOrderNumber}; } push @Filename, $CurSysDTObject->Format( Format => '%F_%H-%M', ); # return the PDF document my $PDFString = $Kernel::OM->Get('Kernel::System::PDF')->DocumentOutput(); return $LayoutObject->Attachment( Filename => join( '_', @Filename ) . '.pdf', ContentType => 'application/pdf', Content => $PDFString, Type => 'inline', ); } # start the document sub _StartDocument { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(HeaderArea HeaderValue)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # title of the PDF-Document my $Product = $Kernel::OM->Get('Kernel::Config')->Get('Product'); my $Title = sprintf '%s: %s#%s', $Product, $Param{HeaderArea}, $Param{HeaderValue}; # create new PDF document $Kernel::OM->Get('Kernel::System::PDF')->DocumentNew( Title => $Title, Encode => $LayoutObject->{UserCharset}, ); return ''; } # output the headline, create a new page sub _OutputHeadline { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(HeaderArea HeaderValue Title TemplatePrefix)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my $PrintedBy = $LayoutObject->{LanguageObject}->Translate('printed by'); my $Time = $LayoutObject->{Time}; my $UserFullName = $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $Self->{UserID}, ); # page controls the PDF-generation # page headers and footer my $Page = $Self->{Page}; $Page->{HeaderRight} = sprintf '%s#%s', $Param{HeaderArea}, $Param{HeaderValue}; $Page->{PageText} = $LayoutObject->{LanguageObject}->Translate('Page'); $Page->{PageCount} = $Self->{Page}->{PageCount} // 1; # create new PDF page $Kernel::OM->Get('Kernel::System::PDF')->PageNew( %{$Page}, FooterRight => $Page->{PageText} . ' ' . $Page->{PageCount}, ); $Self->{Page}->{PageCount}++; # get PDF object my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); $PDFObject->PositionSet( Move => 'relativ', Y => -6, ); # output title $PDFObject->Text( Text => $Param{Title}, FontSize => 13, ); $PDFObject->PositionSet( Move => 'relativ', Y => -6, ); # output "printed by" $PDFObject->Text( Text => $PrintedBy . ' ' . $UserFullName . ' ' . $Time, FontSize => 9, ); $PDFObject->PositionSet( Move => 'relativ', Y => -14, ); return ''; } # a helper for preparing a table row for PDF generation sub _PrepareAndAddInfoRow { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(RowSpec Data)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my ( $RowSpec, $Data ) = @Param{qw(RowSpec Data)}; # short name, just for convenience my $Attribute = $RowSpec->{Attribute}; # get config of frontend module $Self->{Config} = $Kernel::OM->Get('Kernel::Config')->Get("ITSMChange::Frontend::$Self->{Action}"); # skip if row is switched off in SysConfig return if $RowSpec->{IsOptional} && !$Self->{Config}->{$Attribute}; # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # keys are always translatable my $Key = $RowSpec->{Key} || $Attribute; $Key = $LayoutObject->{LanguageObject}->Translate($Key); # determine the value my $Value; if ( $RowSpec->{ValueIsTime} ) { # format the time value $Value = $LayoutObject->Output( Template => '[% Data.' . $Attribute . ' | Localize("TimeLong") %]', Data => $Data, ); } elsif ( $RowSpec->{ValueIsUser} ) { # format the user id if ( $Data->{ $Attribute . 'ID' } ) { my $UserFullName = $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $Data->{ $Attribute . 'ID' }, ); if ($UserFullName) { $Value = $UserFullName; } else { $Value = 'ID=' . $Data->{$Attribute}; } } } elsif ( $RowSpec->{ValueIsDynamicField} ) { $Value = $RowSpec->{Value}; } else { # take value from the passed in data $Value = $Data->{$Attribute}; } # translate the value if ( $Value && $RowSpec->{ValueIsTranslatable} ) { $Value = $LayoutObject->{LanguageObject}->Translate($Value); } # add separator between key and value $Key .= ':'; # show row push @{ $RowSpec->{Table} }, { Key => $Key, Value => $Value, }; return; } # emit information about a change sub _OutputChangeInfo { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(Change PrintWorkOrder)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # just for having shorter names my $Change = $Param{Change}; # fill the two tables on top, # both tables have two colums: Key and Value my ( @TableLeft, @TableRight ); # determine values that can't easily be determined in _PrepareAndAddInfoRow() my %ComplicatedValue; # Values for CAB for my $Attribute (qw(CABAgents CABCustomers)) { my @LongNames; if ( $Attribute eq 'CABAgents' && $Change->{$Attribute} ) { for my $CABAgent ( @{ $Change->{$Attribute} } ) { my $UserFullName = $Kernel::OM->Get('Kernel::System::User')->UserName( UserID => $CABAgent, ); if ($UserFullName) { push @LongNames, $UserFullName; } else { push @LongNames, 'ID=' . $CABAgent; } } } elsif ( $Attribute eq 'CABCustomers' && $Change->{$Attribute} ) { for my $CABCustomer ( @{ $Change->{$Attribute} } ) { my %UserData = $Kernel::OM->Get('Kernel::System::CustomerUser')->CustomerUserDataGet( User => $CABCustomer, Cache => 1, ); if (%UserData) { push @LongNames, $UserData{UserFullname}; } else { push @LongNames, 'ID=' . $CABCustomer; } } } # remember the value $ComplicatedValue{ $Attribute . 'Long' } = join( "\n", @LongNames ) || '-'; } # value for attachments { # get change object my $ChangeObject = $Kernel::OM->Get('Kernel::System::ITSMChange'); my @Attachments = $ChangeObject->ChangeAttachmentList( ChangeID => $Change->{ChangeID}, ); my @Values; ATTACHMENT: for my $Filename (@Attachments) { # get info about file my $AttachmentData = $ChangeObject->ChangeAttachmentGet( ChangeID => $Change->{ChangeID}, Filename => $Filename, ); # check for attachment information next ATTACHMENT if !$AttachmentData; push @Values, sprintf '%s %s', $AttachmentData->{Filename}, $AttachmentData->{Filesize}; } # show row $ComplicatedValue{Attachments} = join( "\n", @Values ) || '-'; } # get the dynamic fields for this screen my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( Valid => 1, ObjectType => [ 'ITSMChange', 'ITSMWorkOrder' ], FieldFilter => $Self->{Config}->{DynamicField} || {}, ); # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # cycle trough the activated Dynamic Fields my @DynamicFieldRowSpec; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # show only change dynamic fields here next DYNAMICFIELD if $DynamicFieldConfig->{ObjectType} ne 'ITSMChange'; # get dynamic field backend object my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); my $Value = $DynamicFieldBackendObject->ValueGet( DynamicFieldConfig => $DynamicFieldConfig, ObjectID => $Change->{ChangeID}, ); # get print string for this dynamic field my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $Value, ValueMaxChars => 1000, LayoutObject => $LayoutObject, ); # for empty values if ( !$ValueStrg->{Value} ) { $ValueStrg->{Value} = '-'; } my $Label = $DynamicFieldConfig->{Label}; push @DynamicFieldRowSpec, { Attribute => $Label, Key => $Label, ValueIsDynamicField => 1, Value => $ValueStrg->{Value}, Table => \@TableLeft, }; } my @RowSpec = ( { Attribute => Translatable('ChangeState'), Table => \@TableLeft, ValueIsTranslatable => 1, }, { Attribute => Translatable('PlannedEffort'), IsOptional => 1, Table => \@TableLeft, }, { Attribute => Translatable('AccountedTime'), IsOptional => 1, Table => \@TableLeft, }, { Attribute => Translatable('Category'), Key => 'Category', Table => \@TableLeft, ValueIsTranslatable => 1, }, { Attribute => Translatable('Impact'), Key => 'Impact', Table => \@TableLeft, ValueIsTranslatable => 1, }, { Attribute => Translatable('Priority'), Key => 'Priority', Table => \@TableLeft, ValueIsTranslatable => 1, }, @DynamicFieldRowSpec, { Attribute => Translatable('ChangeManager'), Table => \@TableLeft, ValueIsUser => 1, }, { Attribute => Translatable('ChangeBuilder'), Table => \@TableLeft, ValueIsUser => 1, }, { Attribute => 'CABAgentsLong', Key => Translatable('CAB Agents'), Table => \@TableLeft, }, { Attribute => 'CABCustomersLong', Key => Translatable('CAB Customers'), Table => \@TableLeft, }, { Attribute => 'Attachments', Key => 'Attachments', Table => \@TableLeft, }, { Attribute => Translatable('RequestedTime'), IsOptional => 1, Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('PlannedStartTime'), Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('PlannedEndTime'), Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('ActualStartTime'), Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('ActualEndTime'), Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('CreateTime'), Key => 'Created', Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('ChangeTime'), Key => 'Changed', Table => \@TableRight, ValueIsTime => 1, }, ); for my $RowSpec (@RowSpec) { # fill @TableLeft and @TableRight $Self->_PrepareAndAddInfoRow( RowSpec => $RowSpec, Data => { %{$Change}, %ComplicatedValue }, ); } # number of rows in the change info table my $Rows = max( scalar(@TableLeft), scalar(@TableRight) ); my %Table; for my $Row ( 0 .. $Rows - 1 ) { $Table{CellData}[$Row][0]{Content} = $TableLeft[$Row]->{Key}; $Table{CellData}[$Row][0]{Font} = 'ProportionalBold'; $Table{CellData}[$Row][1]{Content} = $TableLeft[$Row]->{Value}; $Table{CellData}[$Row][2]{Content} = ' '; $Table{CellData}[$Row][2]{BackgroundColor} = '#FFFFFF'; $Table{CellData}[$Row][3]{Content} = $TableRight[$Row]->{Key}; $Table{CellData}[$Row][3]{Font} = 'ProportionalBold'; $Table{CellData}[$Row][4]{Content} = $TableRight[$Row]->{Value}; } $Table{ColumnData}[0]{Width} = 80; $Table{ColumnData}[1]{Width} = 170.5; $Table{ColumnData}[2]{Width} = 4; $Table{ColumnData}[3]{Width} = 80; $Table{ColumnData}[4]{Width} = 170.5; $Table{Type} = 'Cut'; $Table{Border} = 0; $Table{FontSize} = 6; $Table{BackgroundColorEven} = '#DDDDDD'; $Table{Padding} = 1; $Table{PaddingTop} = 3; $Table{PaddingBottom} = 3; # output table $Self->_PDFOutputTable( Table => \%Table, ); return ''; } # emit information about a workorder sub _OutputWorkOrderInfo { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(Change WorkOrder)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } my ( $WorkOrder, $Change ) = @Param{qw(WorkOrder Change)}; my $PrintWorkOrder = $Param{PrintWorkOrder} || 0; # fill the two tables on top, # both tables have two colums: Key and Value my ( @TableLeft, @TableRight ); # determine values that can't be determined in _PrepareAndAddInfoRow() my %ComplicatedValue; # value for attachments { # get work order object my $WorkOrderObject = $Kernel::OM->Get('Kernel::System::ITSMChange::ITSMWorkOrder'); my @Attachments = $WorkOrderObject->WorkOrderAttachmentList( WorkOrderID => $WorkOrder->{WorkOrderID}, ); my @Values; ATTACHMENT: for my $Filename (@Attachments) { # get info about file my $AttachmentData = $WorkOrderObject->WorkOrderAttachmentGet( WorkOrderID => $WorkOrder->{WorkOrderID}, Filename => $Filename, ); # check for attachment information next ATTACHMENT if !$AttachmentData; push @Values, sprintf '%s %s', $AttachmentData->{Filename}, $AttachmentData->{Filesize}; } # show row $ComplicatedValue{Attachments} = join( "\n", @Values ) || '-'; } # allow wrapping of long words in the change title ( $ComplicatedValue{WrappableChangeTitle} = $Change->{ChangeTitle} ) =~ s{ ( \S{25} ) }{$1 }xmsg; # get config of frontend module $Self->{Config} = $Kernel::OM->Get('Kernel::Config')->Get("ITSMChange::Frontend::$Self->{Action}"); # get the dynamic fields for this screen my $DynamicField = $Kernel::OM->Get('Kernel::System::DynamicField')->DynamicFieldListGet( Valid => 1, ObjectType => [ 'ITSMChange', 'ITSMWorkOrder' ], FieldFilter => $Self->{Config}->{DynamicField} || {}, ); # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my @DynamicFieldRowSpec; DYNAMICFIELD: for my $DynamicFieldConfig ( @{$DynamicField} ) { next DYNAMICFIELD if !IsHashRefWithData($DynamicFieldConfig); # show only workorder dynamic fields here next DYNAMICFIELD if $DynamicFieldConfig->{ObjectType} ne 'ITSMWorkOrder'; # get dynamic field backend object my $DynamicFieldBackendObject = $Kernel::OM->Get('Kernel::System::DynamicField::Backend'); my $Value = $DynamicFieldBackendObject->ValueGet( DynamicFieldConfig => $DynamicFieldConfig, ObjectID => $WorkOrder->{WorkOrderID}, ); # get print string for this dynamic field my $ValueStrg = $DynamicFieldBackendObject->DisplayValueRender( DynamicFieldConfig => $DynamicFieldConfig, Value => $Value, ValueMaxChars => 1000, LayoutObject => $LayoutObject, ); # for empty values if ( !$ValueStrg->{Value} ) { $ValueStrg->{Value} = '-'; } my $Label = $DynamicFieldConfig->{Label}; push @DynamicFieldRowSpec, { Attribute => $Label, Key => $Label, ValueIsDynamicField => 1, Value => $ValueStrg->{Value}, Table => \@TableLeft, }; } my @RowSpec = ( { Attribute => 'WrappableChangeTitle', Table => \@TableLeft, Key => 'ChangeTitle', }, { Attribute => Translatable('ChangeNumber'), Table => \@TableLeft, Key => 'ChangeNumber', }, { Attribute => Translatable('WorkOrderState'), Table => \@TableLeft, ValueIsTranslatable => 1, }, { Attribute => Translatable('WorkOrderType'), Table => \@TableLeft, ValueIsTranslatable => 1, }, { Attribute => Translatable('WorkOrderAgent'), Table => \@TableLeft, ValueIsUser => 1, }, { Attribute => Translatable('PlannedEffort'), IsOptional => 1, Table => \@TableLeft, Key => 'PlannedEffort', }, { Attribute => Translatable('AccountedTime'), IsOptional => 1, Table => \@TableLeft, Key => 'AccountedTime', }, @DynamicFieldRowSpec, { Attribute => Translatable('Attachments'), Key => 'Attachments', Table => \@TableLeft, }, { Attribute => Translatable('PlannedStartTime'), Table => \@TableRight, ValueIsTime => 1, Key => 'PlannedStartTime', }, { Attribute => Translatable('PlannedEndTime'), Table => \@TableRight, ValueIsTime => 1, Key => 'PlannedEndTime', }, { Attribute => Translatable('ActualStartTime'), Table => \@TableRight, ValueIsTime => 1, Key => 'ActualStartTime', }, { Attribute => Translatable('ActualEndTime'), Table => \@TableRight, ValueIsTime => 1, Key => 'ActualEndTime', }, { Attribute => Translatable('CreateTime'), Key => 'Created', Table => \@TableRight, ValueIsTime => 1, }, { Attribute => Translatable('ChangeTime'), Key => 'Changed', Table => \@TableRight, ValueIsTime => 1, }, ); for my $RowSpec (@RowSpec) { # fill @TableLeft and @TableRight # the workorder data overrides the change data $Self->_PrepareAndAddInfoRow( RowSpec => $RowSpec, Data => { %{$Change}, %{$WorkOrder}, %ComplicatedValue }, ); } my $Rows = max( scalar(@TableLeft), scalar(@TableRight) ); my %Table; for my $Row ( 0 .. $Rows - 1 ) { $Table{CellData}[$Row][0]{Content} = $TableLeft[$Row]->{Key}; $Table{CellData}[$Row][0]{Font} = 'ProportionalBold'; $Table{CellData}[$Row][1]{Content} = $TableLeft[$Row]->{Value}; $Table{CellData}[$Row][2]{Content} = ' '; $Table{CellData}[$Row][2]{BackgroundColor} = '#FFFFFF'; $Table{CellData}[$Row][3]{Content} = $TableRight[$Row]->{Key}; $Table{CellData}[$Row][3]{Font} = 'ProportionalBold'; $Table{CellData}[$Row][4]{Content} = $TableRight[$Row]->{Value}; } $Table{ColumnData}[0]{Width} = 80; $Table{ColumnData}[1]{Width} = 170.5; $Table{ColumnData}[2]{Width} = 4; $Table{ColumnData}[3]{Width} = 80; $Table{ColumnData}[4]{Width} = 170.5; $Table{Type} = 'Cut'; $Table{Border} = 0; $Table{FontSize} = 6; $Table{BackgroundColorEven} = '#DDDDDD'; $Table{Padding} = 1; $Table{PaddingTop} = 3; $Table{PaddingBottom} = 3; # output table $Self->_PDFOutputTable( Table => \%Table, ); return ''; } # output a body of text, such as a change description sub _OutputLongText { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(PrintChange PrintWorkOrder Title LongText)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!", ); return; } } # get PDF object my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); # some vertical whitespace $PDFObject->PositionSet( Move => 'relativ', Y => -15, ); # output headline for the section $PDFObject->Text( Text => $Param{Title}, Height => 7, Type => 'Cut', Font => 'ProportionalBoldItalic', FontSize => 7, Color => '#666666', ); # vertical whitespace after title $PDFObject->PositionSet( Move => 'relativ', Y => -4, ); # table params common to printing a body of text, # actually a table is a bit of overkill for a single text, my %Table = ( Type => 'Cut', Border => 0, Font => 'Monospaced', FontSize => 7, BackgroundColor => '#DDDDDD', Padding => 4, PaddingTop => 8, PaddingBottom => 8, ); # output tables $Table{CellData}[0][0]{Content} = $Param{LongText} || ''; # output table $Self->_PDFOutputTable( Table => \%Table, ); return ''; } # output overview over workorders sub _OutputWorkOrderOverview { my ( $Self, %Param ) = @_; # check needed stuff if ( !defined( $Param{WorkOrderOverview} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need WorkOrderOverview!" ); return; } # get PDF object my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); # vertical whitespace before section headline $PDFObject->PositionSet( Move => 'relativ', Y => -15, ); # get laytout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); # output headline for the section my $Translation = $LayoutObject->{LanguageObject}; my $SectionTitle = $Translation->Translate( 'ITSM Workorder Overview (%s)', scalar @{ $Param{WorkOrderOverview} } ); $PDFObject->Text( Text => $SectionTitle, Height => 7, Type => 'Cut', Font => 'ProportionalBoldItalic', FontSize => 7, Color => '#666666', ); # vertical whitespace after section headline $PDFObject->PositionSet( Move => 'relativ', Y => -4, ); # output the overview table only if there is at least a single workorder, # printing an empty table might create havoc if ( @{ $Param{WorkOrderOverview} } ) { my %Table; my $Row = 0; # add table header $Table{CellData}[ $Row++ ] = [ { Font => 'ProportionalBold', Content => '#', }, { Font => 'ProportionalBold', Content => $Translation->Translate('Title'), }, { Font => 'ProportionalBold', Content => $Translation->Translate('State'), }, { Font => 'ProportionalBold', Content => $Translation->Translate('PlannedStartTime'), }, { Font => 'ProportionalBold', Content => $Translation->Translate('PlannedEndTime'), }, { Font => 'ProportionalBold', Content => $Translation->Translate('ActualStartTime'), }, { Font => 'ProportionalBold', Content => $Translation->Translate('ActualEndTime'), }, ]; for my $WorkOrder ( @{ $Param{WorkOrderOverview} } ) { $Table{CellData}[ $Row++ ] = [ map { { Content => $_ } } @{$WorkOrder} ]; } $Table{ColumnData}[0]{Width} = 2; $Table{ColumnData}[1]{Width} = 63; $Table{ColumnData}[2]{Width} = 25; $Table{ColumnData}[3]{Width} = 40; $Table{ColumnData}[4]{Width} = 40; $Table{ColumnData}[5]{Width} = 40; $Table{ColumnData}[6]{Width} = 40; # table params $Table{Type} = 'Cut'; $Table{Border} = 0; $Table{FontSize} = 6; $Table{BackgroundColor} = '#DDDDDD'; $Table{Padding} = 1; $Table{PaddingTop} = 3; $Table{PaddingBottom} = 3; # output table $Self->_PDFOutputTable( Table => \%Table, ); } return 1; } # output info about linked objects of a change or a workorder sub _OutputLinkedObjects { my ( $Self, %Param ) = @_; # check needed stuff for my $Needed (qw(PrintChange PrintWorkOrder LinkData LinkTypeList)) { if ( !defined( $Param{$Needed} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need $Needed!" ); return; } } # get layout object my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout'); my %TypeList = %{ $Param{LinkTypeList} }; my %Table; my $Row = 0; for my $LinkTypeLinkDirection ( sort { lc $a cmp lc $b } keys %{ $Param{LinkData} } ) { # investigate link type name my @LinkData = split q{::}, $LinkTypeLinkDirection; my $LinkTypeName = $TypeList{ $LinkData[0] }->{ $LinkData[1] . 'Name' }; $LinkTypeName = $LayoutObject->{LanguageObject}->Translate($LinkTypeName); # define headline $Table{CellData}[$Row][0]{Content} = $LinkTypeName . ':'; $Table{CellData}[$Row][0]{Font} = 'ProportionalBold'; $Table{CellData}[$Row][1]{Content} = ''; # extract object list my $ObjectList = $Param{LinkData}->{$LinkTypeLinkDirection}; for my $Object ( sort { lc $a cmp lc $b } keys %{$ObjectList} ) { for my $Item ( @{ $ObjectList->{$Object} } ) { $Table{CellData}[$Row][0]{Content} ||= ''; $Table{CellData}[$Row][1]{Content} = $Item->{Title} || ''; } continue { $Row++; } } } $Table{ColumnData}[0]{Width} = 80; $Table{ColumnData}[1]{Width} = 431; # get PDF object my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); # set new position $PDFObject->PositionSet( Move => 'relativ', Y => -15, ); # output headline $PDFObject->Text( Text => $LayoutObject->{LanguageObject}->Translate('Linked Objects'), Height => 7, Type => 'Cut', Font => 'ProportionalBoldItalic', FontSize => 7, Color => '#666666', ); # set new position $PDFObject->PositionSet( Move => 'relativ', Y => -4, ); # table params $Table{Type} = 'Cut'; $Table{Border} = 0; $Table{FontSize} = 6; $Table{BackgroundColor} = '#DDDDDD'; $Table{Padding} = 1; $Table{PaddingTop} = 3; $Table{PaddingBottom} = 3; # output table $Self->_PDFOutputTable( Table => \%Table, ); return 1; } # output a table, accross several pages if neccessary sub _PDFOutputTable { my ( $Self, %Param ) = @_; # check needed stuff if ( !defined( $Param{Table} ) ) { $Kernel::OM->Get('Kernel::System::Log')->Log( Priority => 'error', Message => "Need Table!" ); return; } # just for having shorter names my $Table = $Param{Table}; my $Page = $Self->{Page}; PAGE: for ( $Page->{PageCount} .. $Page->{MaxPages} ) { # get PDF object my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF'); # output table (or a fragment of it) %{$Table} = $PDFObject->Table( %{$Table} ); # stop output or output next page if ( $Table->{State} ) { last PAGE; } else { $PDFObject->PageNew( %{$Page}, FooterRight => join( ' ', $Page->{PageText}, $Page->{PageCount} ), ); $Page->{PageCount}++; } } $Self->{Page}->{PageCount} = $Page->{PageCount}; return 1; } 1;