Files
scripts/Perl OTRS/Kernel/System/Stats/Dynamic/TimeAccounting.pm
2024-10-14 00:08:40 +02:00

905 lines
27 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::Stats::Dynamic::TimeAccounting;
use strict;
use warnings;
use Kernel::Language qw(Translatable);
use Kernel::System::VariableCheck qw(IsArrayRefWithData);
our @ObjectDependencies = (
'Kernel::System::Log',
'Kernel::System::DateTime',
'Kernel::System::TimeAccounting',
'Kernel::System::User',
);
sub new {
my ( $Type, %Param ) = @_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
return $Self;
}
sub GetObjectName {
my ( $Self, %Param ) = @_;
return 'TimeAccounting';
}
sub GetObjectAttributes {
my ( $Self, %Param ) = @_;
my $DateTimeObjectCurrent = $Kernel::OM->Create('Kernel::System::DateTime');
# set predefined start time
my $TimeStamp = $DateTimeObjectCurrent->ToEpoch();
my ($Date) = split /\s+/, $TimeStamp;
my $Today = sprintf "%s 23:59:59", $Date;
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# get project list
my %ProjectList = $TimeAccountingObject->ProjectSettingsGet(
Status => 'valid',
);
# get action list
my %ActionListSource = $TimeAccountingObject->ActionSettingsGet();
my %ActionList;
for my $Action ( sort keys %ActionListSource ) {
$ActionList{$Action} = $ActionListSource{$Action}->{Action};
}
# get user list
my %UserList = $Kernel::OM->Get('Kernel::System::User')->UserList(
Type => 'Long',
Valid => 0,
);
my @Attributes = (
{
Name => Translatable('Project'),
UseAsXvalue => 1,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'Project',
Block => 'MultiSelectField',
Translation => 0,
Values => $ProjectList{Project},
},
{
Name => Translatable('User'),
UseAsXvalue => 1,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'User',
Block => 'MultiSelectField',
Translation => 0,
Values => \%UserList,
},
{
Name => Translatable('Sort sequence'),
UseAsXvalue => 0,
UseAsValueSeries => 1,
UseAsRestriction => 0,
Element => 'SortSequence',
Block => 'SelectField',
Translation => 1,
Values => {
Up => 'ascending',
Down => 'descending',
},
},
{
Name => Translatable('Task'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'ProjectAction',
Block => 'MultiSelectField',
Translation => 0,
Values => \%ActionList,
},
{
Name => Translatable('Period'),
UseAsXvalue => 0,
UseAsValueSeries => 0,
UseAsRestriction => 1,
Element => 'Period',
TimePeriodFormat => 'DateInputFormat', # 'DateInputFormatLong',
Block => 'Time',
Values => {
TimeStart => 'TimeAccountingPeriodStart',
TimeStop => 'TimeAccountingPeriodStop',
},
},
);
return @Attributes;
}
sub GetHeaderLine {
my ( $Self, %Param ) = @_;
my @HeaderLine = ("");
# Users as X-value
if ( $Param{XValue}->{Element} && $Param{XValue}->{Element} eq 'User' ) {
# user have been selected as x-value
my @UserIDs = @{ $Param{XValue}->{SelectedValues} };
# get user object
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
# iterate over selected users
USERID:
for my $UserID (@UserIDs) {
my $UserName = $UserObject->UserName(
UserID => $UserID,
);
push @HeaderLine, $UserName;
}
}
# Projects as X-value
else {
# projects have been selected as x-value
my @ProjectIDs = @{ $Param{XValue}->{SelectedValues} };
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# iterate over selected projects
PROJECTID:
for my $ProjectID (@ProjectIDs) {
my %ProjectData = $TimeAccountingObject->ProjectGet(
ID => $ProjectID,
);
push @HeaderLine, $ProjectData{Project};
}
}
return \@HeaderLine;
}
sub GetStatTable {
my ( $Self, %Param ) = @_;
my @StatArray;
my @UserIDs;
# Users as X-value
if ( $Param{XValue}->{Element} && $Param{XValue}->{Element} eq 'User' ) {
# user have been selected as x-value
@UserIDs = @{ $Param{XValue}->{SelectedValues} };
# get stat data
my $StatData = $Self->_GetStatData(
Param => \%Param,
UserIDs => \@UserIDs,
);
# check stat data
return if !$StatData;
return if ref $StatData ne 'ARRAY';
my @RawStatArray = @{$StatData};
return if !@RawStatArray;
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# get list of needed data
my %ProjectData = $TimeAccountingObject->ProjectSettingsGet();
my %ProjectList = %{ $ProjectData{Project} || {} };
my %ActionData = $TimeAccountingObject->ActionSettingsGet();
my %ActionList = map { ( $_ => $ActionData{$_}->{Action} ) } keys %ActionData;
my @SortedProjectIDs = sort { $ProjectList{$a} cmp $ProjectList{$b} } keys %ProjectList;
my @SortedActionIDs = sort { $ActionList{$a} cmp $ActionList{$b} } keys %ActionList;
# re-sort projects depending on selected sequence
if (
IsArrayRefWithData( $Param{ValueSeries} )
&& $Param{ValueSeries}->[0]->{SelectedValues}->[0] eq 'Down'
)
{
@SortedProjectIDs = reverse @SortedProjectIDs;
}
# iterate over sorted project list
SORTEDPROJECTID:
for my $SortedProjectID (@SortedProjectIDs) {
# check for unselected projects
if (
$Param{Restrictions}->{Project}
&& !grep { $_ == $SortedProjectID } @{ $Param{Restrictions}->{Project} || [] }
)
{
next SORTEDPROJECTID;
}
# get the current project data of current project
my @ProjectStatData = grep { $_->{ProjectID} == $SortedProjectID } @RawStatArray;
# iterate over sorted action list
SORTEDACTIONID:
for my $SortedActionID (@SortedActionIDs) {
# check for unselected actions
if (
$Param{Restrictions}->{ProjectAction}
&& !grep { $_ == $SortedActionID } @{ $Param{Restrictions}->{ProjectAction} || [] }
)
{
next SORTEDACTIONID;
}
# get the current action out of the current project
my @ActionStatData = grep { $_->{ActionID} == $SortedActionID } @ProjectStatData;
my @RowData;
# add descriptive first column
my $RowLabel = "$ProjectList{$SortedProjectID}::$ActionList{$SortedActionID}";
push @RowData, $RowLabel;
# iterate over selected users
USERID:
for my $UserID (@UserIDs) {
# at least get '0' for user data
my $UserPeriodSum = 0;
# iterate over period data of user
for my $PeriodData ( grep { $_->{UserID} == $UserID } @ActionStatData ) {
$UserPeriodSum += $PeriodData->{Period};
}
# safe user data to row data
push @RowData, $UserPeriodSum;
}
# store current row to global stat array
push @StatArray, \@RowData;
}
}
}
# Projects as X-value
else {
# projects have been selected as x-value
my @ProjectIDs = @{ $Param{XValue}->{SelectedValues} };
# get user object
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
# we need to get all users
my %UserIDs = $UserObject->UserList(
Type => 'Short',
Valid => 1,
);
@UserIDs = keys %UserIDs;
# get calculated stats data
my $StatData = $Self->_GetStatData(
Param => \%Param,
UserIDs => \@UserIDs,
);
# check stat data
return if !$StatData;
return if ref $StatData ne 'ARRAY';
my @RawStatArray = @{$StatData};
return if !@RawStatArray;
# get list of needed data
my %UserList = $UserObject->UserList(
Type => 'Long',
Valid => 1,
);
my @SortedUserIDs = sort { $UserList{$a} cmp $UserList{$b} } keys %UserList;
# re-sort users depending on selected sequence
if (
IsArrayRefWithData( $Param{ValueSeries} )
&& $Param{ValueSeries}->[0]->{SelectedValues}->[0] eq 'Down'
)
{
@SortedUserIDs = reverse @SortedUserIDs;
}
# iterate over sorted user list
SORTEDUSERID:
for my $SortedUserID (@SortedUserIDs) {
# check for unselected users
if (
$Param{Restrictions}->{User}
&& !grep { $_ == $SortedUserID } @{ $Param{Restrictions}->{User} || [] }
)
{
next SORTEDUSERID;
}
# get the current user data of current user
my @UserStatData = grep { $_->{UserID} == $SortedUserID } @RawStatArray;
my @RowData;
# add descriptive first column
my $RowLabel = $UserList{$SortedUserID};
push @RowData, $RowLabel;
# iterate over selected projects
PROJECTID:
for my $ProjectID (@ProjectIDs) {
# at least get '0' for user data
my $ProjectPeriodSum = 0;
# iterate over period data of user
for my $PeriodData ( grep { $_->{ProjectID} == $ProjectID } @UserStatData ) {
$ProjectPeriodSum += $PeriodData->{Period};
}
# safe user data to row data
push @RowData, $ProjectPeriodSum;
}
# store current row to global stat array
push @StatArray, \@RowData;
}
}
return @StatArray;
}
sub GetStatTablePreview {
my ( $Self, %Param ) = @_;
my @StatArray;
my @UserIDs;
# Users as X-value
if ( $Param{XValue}->{Element} && $Param{XValue}->{Element} eq 'User' ) {
# user have been selected as x-value
@UserIDs = @{ $Param{XValue}->{SelectedValues} };
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# get list of needed data
my %ProjectData = $TimeAccountingObject->ProjectSettingsGet();
my %ProjectList = %{ $ProjectData{Project} || {} };
my %ActionData = $TimeAccountingObject->ActionSettingsGet();
my %ActionList = map { ( $_ => $ActionData{$_}->{Action} ) } keys %ActionData;
my @SortedProjectIDs = sort { $ProjectList{$a} cmp $ProjectList{$b} } keys %ProjectList;
my @SortedActionIDs = sort { $ActionList{$a} cmp $ActionList{$b} } keys %ActionList;
# re-sort projects depending on selected sequence
if (
IsArrayRefWithData( $Param{ValueSeries} )
&& $Param{ValueSeries}->[0]->{SelectedValues}->[0] eq 'Down'
)
{
@SortedProjectIDs = reverse @SortedProjectIDs;
}
# iterate over sorted project list
SORTEDPROJECTID:
for my $SortedProjectID (@SortedProjectIDs) {
# check for unselected projects
if (
$Param{Restrictions}->{Project}
&& !grep { $_ == $SortedProjectID } @{ $Param{Restrictions}->{Project} || [] }
)
{
next SORTEDPROJECTID;
}
# iterate over sorted action list
SORTEDACTIONID:
for my $SortedActionID (@SortedActionIDs) {
# check for unselected actions
if (
$Param{Restrictions}->{ProjectAction}
&& !grep { $_ == $SortedActionID } @{ $Param{Restrictions}->{ProjectAction} || [] }
)
{
next SORTEDACTIONID;
}
my @RowData;
# add descriptive first column
my $RowLabel = "$ProjectList{$SortedProjectID}::$ActionList{$SortedActionID}";
push @RowData, $RowLabel;
# iterate over selected users
USERID:
for my $UserID (@UserIDs) {
# safe user data to row data
push @RowData, int rand 50;
}
# store current row to global stat array
push @StatArray, \@RowData;
}
}
}
# Projects as X-value
else {
# projects have been selected as x-value
my @ProjectIDs = @{ $Param{XValue}{SelectedValues} };
# get user object
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
# we need to get all users
my %UserIDs = $UserObject->UserList(
Type => 'Short',
Valid => 1,
);
@UserIDs = keys %UserIDs;
# get list of needed data
my %UserList = $UserObject->UserList(
Type => 'Long',
Valid => 1,
);
my @SortedUserIDs = sort { $UserList{$a} cmp $UserList{$b} } keys %UserList;
# re-sort users depending on selected sequence
if (
IsArrayRefWithData( $Param{ValueSeries} )
&& $Param{ValueSeries}->[0]->{SelectedValues}->[0] eq 'Down'
)
{
@SortedUserIDs = reverse @SortedUserIDs;
}
# iterate over sorted user list
SORTEDUSERID:
for my $SortedUserID (@SortedUserIDs) {
# check for unselected users
if (
$Param{Restrictions}->{User}
&& !grep { $_ == $SortedUserID } @{ $Param{Restrictions}->{User} || [] }
)
{
next SORTEDUSERID;
}
my @RowData;
# add descriptive first column
my $RowLabel = $UserList{$SortedUserID};
push @RowData, $RowLabel;
# iterate over selected projects
PROJECTID:
for my $ProjectID (@ProjectIDs) {
# safe user data to row data
push @RowData, int rand 50;
}
# store current row to global stat array
push @StatArray, \@RowData;
}
}
return @StatArray;
}
sub ExportWrapper {
my ( $Self, %Param ) = @_;
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# wrap ids to used spelling
for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) {
ELEMENT:
for my $Element ( @{ $Param{$Use} } ) {
next ELEMENT if !$Element;
next ELEMENT if !$Element->{SelectedValues};
my $ElementName = $Element->{Element};
my $Values = $Element->{SelectedValues};
if ( $ElementName eq 'User' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
$ID->{Content} = $UserObject->UserLookup(
UserID => $ID->{Content}
);
}
}
elsif ( $ElementName eq 'Project' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my %TmpProjectData = $TimeAccountingObject->ProjectGet(
ID => $ID->{Content}
);
$ID->{Content} = $TmpProjectData{Project};
}
}
elsif ( $ElementName eq 'ProjectAction' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my %TmpActionData = $TimeAccountingObject->ActionGet(
ID => $ID->{Content}
);
$ID->{Content} = $TmpActionData{Action};
}
}
}
}
return \%Param;
}
sub ImportWrapper {
my ( $Self, %Param ) = @_;
# get needed objects
my $UserObject = $Kernel::OM->Get('Kernel::System::User');
my $LogObject = $Kernel::OM->Get('Kernel::System::Log');
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# wrap used spelling to ids
for my $Use (qw(UseAsValueSeries UseAsRestriction UseAsXvalue)) {
ELEMENT:
for my $Element ( @{ $Param{$Use} } ) {
next ELEMENT if !$Element;
next ELEMENT if !$Element->{SelectedValues};
my $ElementName = $Element->{Element};
my $Values = $Element->{SelectedValues};
if ( $ElementName eq 'User' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my $UserID = $UserObject->UserLookup(
UserLogin => $ID->{Content},
);
if ($UserID) {
$ID->{Content} = $UserID;
}
else {
$LogObject->Log(
Priority => 'error',
Message => "Import: Can' find the user $ID->{Content}!"
);
$ID = undef;
}
}
}
elsif ( $ElementName eq 'Project' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my %Project = $TimeAccountingObject->ProjectGet(
Project => $ID->{Content},
);
if ( $Project{ID} ) {
$ID->{Content} = $Project{ID};
}
else {
$LogObject->Log(
Priority => 'error',
Message => "Import: Can' find project $ID->{Content}!"
);
$ID = undef;
}
}
}
elsif ( $ElementName eq 'ProjectAction' ) {
ID:
for my $ID ( @{$Values} ) {
next ID if !$ID;
my %Action = $TimeAccountingObject->ActionGet(
Action => $ID->{Content},
);
if ( $Action{ID} ) {
$ID->{Content} = $Action{ID};
}
else {
$LogObject->Log(
Priority => 'error',
Message => "Import: Can' find action $ID->{Content}!"
);
$ID = undef;
}
}
}
}
}
return \%Param;
}
sub GetExtendedTitle {
my ( $Self, %Param ) = @_;
return if $Param{Restrictions}->{TimeAccountingPeriodStart} || $Param{Restrictions}->{TimeAccountingPeriodStop};
my %DateIndexToName = (
'Second' => 0,
'Minute' => 1,
'Hour' => 2,
'Day' => 3,
'Month' => 4,
'Year' => 5,
);
my %PreviousMonthDates = $Self->_GetPreviousMonthDates(
DateIndexToName => \%DateIndexToName,
);
my $StartDate = sprintf "%04d-%02d-%02d 00:00:00", $PreviousMonthDates{NewStartDate}->[0],
$PreviousMonthDates{NewStartDate}->[1], $PreviousMonthDates{NewStartDate}->[2];
my $StopDate = sprintf "%04d-%02d-%02d 00:00:00", $PreviousMonthDates{NewStopDate}->[0],
$PreviousMonthDates{NewStopDate}->[1], $PreviousMonthDates{NewStopDate}->[2];
return "$StartDate-$StopDate";
}
sub _GetStatData {
my ( $Self, %Param ) = @_;
my @Return;
my @UserIDs = @{ $Param{UserIDs} || [] };
my %DateIndexToName = (
'Second' => 0,
'Minute' => 1,
'Hour' => 2,
'Day' => 3,
'Month' => 4,
'Year' => 5,
);
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
# looping over all or selected users
for my $UserID (@UserIDs) {
my $StartDate;
my $StopDate;
# check if time period has been selected
if ( $Param{Param}->{Restrictions}->{TimeAccountingPeriodStart} ) {
# get UNIX time-stamp of start and end values
$StartDate = $TimeAccountingObject->TimeStamp2SystemTime(
String => $Param{Param}->{Restrictions}->{TimeAccountingPeriodStart},
);
$StopDate = $TimeAccountingObject->TimeStamp2SystemTime(
String => $Param{Param}->{Restrictions}->{TimeAccountingPeriodStop},
);
}
else {
# IMPORTANT:
# If no time period had been selected previous month will be used as period!
my %PreviousMonthDates = $Self->_GetPreviousMonthDates(
DateIndexToName => \%DateIndexToName,
);
# Calculate UNIX timestamps for start and stop date.
$StartDate = $TimeAccountingObject->Date2SystemTime(
Year => $PreviousMonthDates{NewStartDate}->[0],
Month => $PreviousMonthDates{NewStartDate}->[1],
Day => $PreviousMonthDates{NewStartDate}->[2],
Hour => 0,
Minute => 0,
Second => 0,
);
$StopDate = $TimeAccountingObject->Date2SystemTime(
Year => $PreviousMonthDates{NewStopDate}->[0],
Month => $PreviousMonthDates{NewStopDate}->[1],
Day => $PreviousMonthDates{NewStopDate}->[2],
Hour => 23,
Minute => 59,
Second => 59,
);
}
# calculate number of days within the given range
my $Days = int( ( $StopDate - $StartDate ) / 86400 ) + 1;
DAY:
for my $Day ( 0 .. $Days ) {
# get day relative to start date
my $DateOfPeriod = $StartDate + $Day * 86400;
# get needed date values out of time-stamp
my @DateValues = $TimeAccountingObject->SystemTime2Date(
SystemTime => $DateOfPeriod,
);
# get working unit for user and day
my %WorkingUnit = $TimeAccountingObject->WorkingUnitsGet(
Year => $DateValues[ $DateIndexToName{'Year'} ],
Month => $DateValues[ $DateIndexToName{'Month'} ],
Day => $DateValues[ $DateIndexToName{'Day'} ],
UserID => $UserID,
);
# extract detailed information
my @DayWorkingUnits = @{ $WorkingUnit{WorkingUnits} || [] };
# check for project restrictions
if (
$Param{Param}->{Restrictions}->{Project}
&& ref $Param{Param}->{Restrictions}->{Project} eq 'ARRAY'
)
{
# build matching hash for selected projects
my %SelectedProjectIDs = map { ( $_ => 1 ) } @{ $Param{Param}->{Restrictions}->{Project} };
# filter only selected projects
my @FilteredProjectWUs = grep {
$SelectedProjectIDs{ $_->{ProjectID} }
} @DayWorkingUnits;
@DayWorkingUnits = @FilteredProjectWUs;
}
# check for task restrictions
if (
$Param{Param}->{Restrictions}->{ProjectAction}
&& ref $Param{Param}->{Restrictions}->{ProjectAction} eq 'ARRAY'
)
{
# build matching hash for selected actions
my %SelectedActionIDs = map { ( $_ => 1 ) } @{ $Param{Param}->{Restrictions}->{ProjectAction} };
# filter only selected actions
my @FilteredActionWUs = grep { $SelectedActionIDs{ $_->{ActionID} } } @DayWorkingUnits;
@DayWorkingUnits = @FilteredActionWUs;
}
# check for user restrictions
if (
$Param{Param}->{Restrictions}->{User}
&& ref $Param{Param}->{Restrictions}->{User} eq 'ARRAY'
)
{
# build matching hash for selected actions
my %SelectedUserIDs = map { ( $_ => 1 ) } @{ $Param{Param}->{Restrictions}->{User} };
# filter only selected actions
my @FilteredUserWUs = grep { $SelectedUserIDs{ $_->{UserID} } } @DayWorkingUnits;
@DayWorkingUnits = @FilteredUserWUs;
}
# do not store data if no data is available
next DAY if !@DayWorkingUnits;
# add data to global result set
push @Return, @DayWorkingUnits;
}
}
return \@Return;
}
sub _GetPreviousMonthDates {
my ( $Self, %Param ) = @_;
if ( !$Param{DateIndexToName} ) {
$Kernel::OM->Get('Kernel::System::Log')->Log(
Priority => 'error',
Message => "Need DateIndexToName!",
);
return;
}
# get time accounting object
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
my $DateTimeObjectCurrent = $Kernel::OM->Create('Kernel::System::DateTime');
# Get current date values.
my @CurrentDate = $TimeAccountingObject->SystemTime2Date(
SystemTime => $DateTimeObjectCurrent->ToEpoch(),
);
# Get first day of previous month.
my @NewStartDate = $TimeAccountingObject->AddDeltaYMD(
$CurrentDate[ $Param{DateIndexToName}->{'Year'} ],
$CurrentDate[ $Param{DateIndexToName}->{'Month'} ],
1,
0,
-1,
0,
);
# Get first day of next month relative to previous month.
my @NewStopDate = $TimeAccountingObject->AddDeltaYMD(
$NewStartDate[0],
$NewStartDate[1],
$NewStartDate[2],
0,
+1,
0,
);
# Get last of day previous month.
@NewStopDate = $TimeAccountingObject->AddDeltaYMD(
$NewStopDate[0],
$NewStopDate[1],
$NewStopDate[2],
0,
0,
-1,
);
return (
NewStartDate => \@NewStartDate,
NewStopDate => \@NewStopDate,
);
}
1;