1668 lines
56 KiB
Perl
1668 lines
56 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::Modules::AgentTimeAccountingEdit;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Kernel::System::VariableCheck qw(IsArrayRefWithData);
|
|
use Kernel::Language qw(Translatable);
|
|
|
|
our $ObjectManagerDisabled = 1;
|
|
|
|
sub new {
|
|
my ( $Type, %Param ) = @_;
|
|
|
|
# allocate new hash for object
|
|
my $Self = {%Param};
|
|
bless( $Self, $Type );
|
|
|
|
my $DateTimeObject = $Kernel::OM->Create('Kernel::System::DateTime');
|
|
|
|
$Self->{TimeZone} = $Param{TimeZone}
|
|
|| $Param{UserTimeZone}
|
|
|| $DateTimeObject->OTRSTimeZoneGet();
|
|
|
|
return $Self;
|
|
}
|
|
|
|
sub PreRun {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# permission check
|
|
return 1 if !$Self->{AccessRo};
|
|
|
|
my $DateTimeObjectCurrent = $Kernel::OM->Create('Kernel::System::DateTime');
|
|
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
|
|
|
|
my ( $Sec, $Min, $Hour, $Day, $Month, $Year ) = $TimeAccountingObject->SystemTime2Date(
|
|
SystemTime => $DateTimeObjectCurrent->ToEpoch(),
|
|
);
|
|
|
|
my %User = $TimeAccountingObject->UserCurrentPeriodGet(
|
|
Year => $Year,
|
|
Month => $Month,
|
|
Day => $Day,
|
|
);
|
|
|
|
return if !$User{ $Self->{UserID} };
|
|
|
|
my %IncompleteWorkingDays = $TimeAccountingObject->WorkingUnitsCompletnessCheck(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
# redirect if incomplete working day are out of range
|
|
if (
|
|
$IncompleteWorkingDays{EnforceInsert}
|
|
&& $Self->{Action} ne 'AgentTimeAccountingEdit'
|
|
&& $Self->{Action} !~ /AgentTimeAccounting/
|
|
)
|
|
{
|
|
|
|
return $Kernel::OM->Get('Kernel::Output::HTML::Layout')->Redirect(
|
|
OP => 'Action=AgentTimeAccountingEdit',
|
|
);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
sub Run {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my @MonthArray = (
|
|
'', 'January', 'February', 'March', 'April', 'May',
|
|
'June', 'July', 'August', 'September', 'October', 'November',
|
|
'December',
|
|
);
|
|
my @WeekdayArray = ( 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', );
|
|
|
|
# get needed objects
|
|
my $ParamObject = $Kernel::OM->Get('Kernel::System::Web::Request');
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
|
|
my $DateTimeObjectCurrent = $Kernel::OM->Create('Kernel::System::DateTime');
|
|
|
|
# ---------------------------------------------------------- #
|
|
# show confirmation dialog to delete the entry of this day
|
|
# ---------------------------------------------------------- #
|
|
if ( $ParamObject->GetParam( Param => 'DeleteDialog' ) ) {
|
|
|
|
my ( $Sec, $Min, $Hour, $Day, $Month, $Year ) = $TimeAccountingObject->SystemTime2Date(
|
|
SystemTime => $DateTimeObjectCurrent->ToEpoch(),
|
|
);
|
|
|
|
# get params
|
|
for my $Parameter (qw(Status Year Month Day)) {
|
|
$Param{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
|
|
}
|
|
|
|
# Check Date
|
|
if ( !$Param{Year} || !$Param{Month} || !$Param{Day} ) {
|
|
$Param{Year} = $Year;
|
|
$Param{Month} = $Month;
|
|
$Param{Day} = $Day;
|
|
}
|
|
else {
|
|
$Param{Year} = sprintf( "%02d", $Param{Year} );
|
|
$Param{Month} = sprintf( "%02d", $Param{Month} );
|
|
$Param{Day} = sprintf( "%02d", $Param{Day} );
|
|
}
|
|
|
|
my $Output = $LayoutObject->Output(
|
|
Data => {%Param},
|
|
TemplateFile => 'AgentTimeAccountingDelete',
|
|
);
|
|
|
|
# build the returned data structure
|
|
my %Data = (
|
|
HTML => $Output,
|
|
DialogType => 'Confirmation',
|
|
);
|
|
|
|
# return JSON-String because of AJAX-Mode
|
|
my $OutputJSON = $LayoutObject->JSONEncode(
|
|
Data => \%Data,
|
|
);
|
|
|
|
return $LayoutObject->Attachment(
|
|
ContentType => 'application/json; charset=' . $LayoutObject->{Charset},
|
|
Content => $OutputJSON,
|
|
Type => 'inline',
|
|
NoCache => 1,
|
|
);
|
|
}
|
|
|
|
# ---------------------------------------------------------- #
|
|
# delete object from database
|
|
# ---------------------------------------------------------- #
|
|
if ( $Self->{Subaction} eq 'Delete' ) {
|
|
for my $Parameter (qw(Day Month Year)) {
|
|
$Param{$Parameter} = $ParamObject->GetParam( Param => $Parameter );
|
|
}
|
|
|
|
if ( !$Self->{AccessRo} ) {
|
|
return $LayoutObject->NoPermission(
|
|
WithHeader => 'yes',
|
|
);
|
|
}
|
|
|
|
if (
|
|
!$TimeAccountingObject->WorkingUnitsDelete(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
UserID => $Self->{UserID},
|
|
)
|
|
)
|
|
{
|
|
|
|
return $LayoutObject->ErrorScreen();
|
|
}
|
|
|
|
return $LayoutObject->Redirect(
|
|
OP =>
|
|
"Action=$Self->{Action};Year=$Param{Year};Month=$Param{Month};Day=$Param{Day}",
|
|
);
|
|
}
|
|
|
|
#---------------------------------------------------------- #
|
|
# mass entry
|
|
# ---------------------------------------------------------- #
|
|
elsif ( $Self->{Subaction} eq 'MassEntry' ) {
|
|
|
|
# permission check
|
|
if ( !$Self->{AccessRo} ) {
|
|
return $LayoutObject->NoPermission(
|
|
WithHeader => 'yes',
|
|
);
|
|
}
|
|
|
|
# get params
|
|
for my $Parameter (qw(Dates LeaveDay Sick Overtime)) {
|
|
$Param{$Parameter} = $ParamObject->GetParam( Param => $Parameter ) || '';
|
|
}
|
|
|
|
# split up dates
|
|
my @Dates = split /[|]/, $Param{Dates};
|
|
my $InsertError = 0;
|
|
|
|
# save entries in the db
|
|
for my $Date (@Dates) {
|
|
|
|
my ( $Year, $Month, $Day ) = split /[-]/, $Date;
|
|
|
|
if (
|
|
!$TimeAccountingObject->WorkingUnitsInsert(
|
|
Year => $Year,
|
|
Month => $Month,
|
|
Day => $Day,
|
|
LeaveDay => $Param{LeaveDay} || 0,
|
|
Sick => $Param{Sick} || 0,
|
|
Overtime => $Param{Overtime} || 0,
|
|
UserID => $Self->{UserID},
|
|
)
|
|
)
|
|
{
|
|
$InsertError = 1;
|
|
}
|
|
|
|
}
|
|
|
|
# redirect to edit screen with log message
|
|
return $LayoutObject->Redirect(
|
|
OP => 'Action=AgentTimeAccountingEdit;Notification='
|
|
. ( $InsertError ? 'Error' : 'Successful' ),
|
|
);
|
|
|
|
}
|
|
|
|
# ---------------------------------------------------------- #
|
|
# edit the time accounting elements
|
|
# ---------------------------------------------------------- #
|
|
|
|
# permission check
|
|
if ( !$Self->{AccessRo} ) {
|
|
return $LayoutObject->NoPermission(
|
|
WithHeader => 'yes',
|
|
);
|
|
}
|
|
|
|
# get params
|
|
for my $Parameter (qw(Status Year Month Day Notification)) {
|
|
$Param{$Parameter} = $ParamObject->GetParam( Param => $Parameter ) || '';
|
|
}
|
|
$Param{RecordsNumber} = $ParamObject->GetParam( Param => 'RecordsNumber' ) || 8;
|
|
$Param{InsertWorkingUnits} = $ParamObject->GetParam( Param => 'InsertWorkingUnits' );
|
|
|
|
my ( $Sec, $Min, $Hour, $Day, $Month, $Year ) = $TimeAccountingObject->SystemTime2Date(
|
|
SystemTime => $DateTimeObjectCurrent->ToEpoch(),
|
|
);
|
|
|
|
# Check Date
|
|
if ( !$Param{Year} || !$Param{Month} || !$Param{Day} ) {
|
|
$Param{Year} = $Year;
|
|
$Param{Month} = $Month;
|
|
$Param{Day} = $Day;
|
|
}
|
|
else {
|
|
$Param{Year} = sprintf( "%02d", $Param{Year} );
|
|
$Param{Month} = sprintf( "%02d", $Param{Month} );
|
|
$Param{Day} = sprintf( "%02d", $Param{Day} );
|
|
}
|
|
|
|
# check if the given date is a valid date
|
|
# if not valid, set the date to today
|
|
my $DateTimeValid = $DateTimeObjectCurrent->Validate(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
Hour => 0,
|
|
Minute => 0,
|
|
Second => 0,
|
|
TimeZone => 'UTC',
|
|
);
|
|
|
|
if ( !$DateTimeValid ) {
|
|
$Param{Year} = $Year;
|
|
$Param{Month} = $Month;
|
|
$Param{Day} = $Day;
|
|
$Param{'WrongDate'} = 1;
|
|
}
|
|
|
|
my %User = $TimeAccountingObject->UserCurrentPeriodGet(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
);
|
|
|
|
# for initial use, the first agent with rw-right will be redirected
|
|
# to 'Setting', so he can do the initial settings
|
|
if ( !$User{ $Self->{UserID} } ) {
|
|
return $Self->_FirstUserRedirect();
|
|
}
|
|
|
|
my %IncompleteWorkingDays = $TimeAccountingObject->WorkingUnitsCompletnessCheck(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
my $MaxAllowedInsertDays = $ConfigObject->Get('TimeAccounting::MaxAllowedInsertDays') || '10';
|
|
( $Param{YearAllowed}, $Param{MonthAllowed}, $Param{DayAllowed} )
|
|
= $TimeAccountingObject->AddDeltaYMD( $Year, $Month, $Day, 0, 0, -$MaxAllowedInsertDays );
|
|
|
|
my $DateTimeObjectGiven = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
}
|
|
);
|
|
|
|
my $DateTimeObjectAllowed = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Year => $Param{YearAllowed},
|
|
Month => $Param{MonthAllowed},
|
|
Day => $Param{DayAllowed},
|
|
}
|
|
);
|
|
|
|
if ( $DateTimeObjectGiven->Compare( DateTimeObject => $DateTimeObjectAllowed ) < 0 ) {
|
|
if ( !$IncompleteWorkingDays{Incomplete}{ $Param{Year} }{ $Param{Month} }{ $Param{Day} } ) {
|
|
return $LayoutObject->Redirect(
|
|
OP =>
|
|
"Action=AgentTimeAccountingView;Year=$Param{Year};Month=$Param{Month};Day=$Param{Day}",
|
|
);
|
|
}
|
|
}
|
|
|
|
# store last screen
|
|
$Kernel::OM->Get('Kernel::System::AuthSession')->UpdateSessionID(
|
|
SessionID => $Self->{SessionID},
|
|
Key => 'LastScreen',
|
|
Value =>
|
|
"Action=$Self->{Action};Year=$Param{Year};Month=$Param{Month};Day=$Param{Day}",
|
|
);
|
|
|
|
$Param{Month_to_Text} = $MonthArray[ $Param{Month} ];
|
|
|
|
( $Param{YearBack}, $Param{MonthBack}, $Param{DayBack} )
|
|
= $TimeAccountingObject->AddDeltaYMD( $Param{Year}, $Param{Month}, $Param{Day}, 0, 0, -1 );
|
|
( $Param{YearNext}, $Param{MonthNext}, $Param{DayNext} )
|
|
= $TimeAccountingObject->AddDeltaYMD( $Param{Year}, $Param{Month}, $Param{Day}, 0, 0, 1 );
|
|
|
|
my $ReduceTimeRef = $ConfigObject->Get('TimeAccounting::ReduceTime');
|
|
|
|
# hashes to store server side errors
|
|
my %Errors = ();
|
|
my %ServerErrorData = ();
|
|
my $ErrorIndex = 1;
|
|
|
|
my %Data;
|
|
my %ActionList = $Self->_ActionList();
|
|
|
|
# Edit Working Units
|
|
if ( $Param{Status} ) {
|
|
|
|
# arrays to save all start and end times for some checks
|
|
my ( @StartTimes, @EndTimes );
|
|
|
|
# delete previous entries for this day and user
|
|
$TimeAccountingObject->WorkingUnitsDelete(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
my %CheckboxCheck = ();
|
|
for my $Element (qw(LeaveDay Sick Overtime)) {
|
|
my $Value = $ParamObject->GetParam( Param => $Element );
|
|
if ($Value) {
|
|
$CheckboxCheck{$Element} = 1;
|
|
$Param{$Element} = 'checked="checked"';
|
|
}
|
|
else {
|
|
$Param{$Element} = '';
|
|
}
|
|
}
|
|
|
|
# if more than one check box was checked it is a server error
|
|
if ( scalar keys %CheckboxCheck > 1 ) {
|
|
for my $Checkbox ( sort keys %CheckboxCheck ) {
|
|
$Errors{ $Checkbox . 'Invalid' } = 'ServerError';
|
|
}
|
|
}
|
|
else {
|
|
|
|
# insert values (if any)
|
|
if ( scalar keys %CheckboxCheck > 0 ) {
|
|
if (
|
|
!$TimeAccountingObject->WorkingUnitsInsert(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
LeaveDay => $CheckboxCheck{LeaveDay} || 0,
|
|
Sick => $CheckboxCheck{Sick} || 0,
|
|
Overtime => $CheckboxCheck{Overtime} || 0,
|
|
UserID => $Self->{UserID},
|
|
)
|
|
)
|
|
{
|
|
return $LayoutObject->ErrorScreen(
|
|
Message => Translatable('Can\'t insert Working Units!'),
|
|
);
|
|
}
|
|
$Param{SuccessfulInsert} = 1;
|
|
}
|
|
}
|
|
|
|
# get check item object
|
|
my $CheckItemObject = $Kernel::OM->Get('Kernel::System::CheckItem');
|
|
|
|
ID:
|
|
for my $ID ( 1 .. $Param{RecordsNumber} ) {
|
|
|
|
# arrays to save the server errors block to show the error messages
|
|
my ( @StartTimeServerErrorBlock, @EndTimeServerErrorBlock, @PeriodServerErrorBlock ) = ();
|
|
|
|
for my $Parameter (qw(ProjectID ActionID Remark StartTime EndTime Period)) {
|
|
$Param{$Parameter} = $ParamObject->GetParam( Param => $Parameter . '[' . $ID . ']' );
|
|
if ( $Param{$Parameter} ) {
|
|
my $ParamRef = \$Param{$Parameter};
|
|
|
|
# delete leading and tailing spaces
|
|
$ParamRef = $CheckItemObject->StringClean(
|
|
StringRef => $ParamRef,
|
|
TrimLeft => 1,
|
|
TrimRight => 1,
|
|
);
|
|
}
|
|
}
|
|
|
|
next ID if !$Param{ProjectID} && !$Param{ActionID};
|
|
|
|
# check for missing values
|
|
if ( $Param{ProjectID} && !$Param{ActionID} ) {
|
|
$Errors{$ErrorIndex}{ActionIDInvalid} = 'ServerError';
|
|
}
|
|
if ( !$Param{ProjectID} && $Param{ActionID} ) {
|
|
$Errors{$ErrorIndex}{ProjectIDInvalid} = 'ServerError';
|
|
}
|
|
|
|
# create a valid period
|
|
my $Period = $Param{Period};
|
|
if ( $Period =~ /^(\d+),(\d+)/ ) {
|
|
$Period = $1 . "." . $2;
|
|
}
|
|
|
|
#allow format hh:mm
|
|
elsif ( $Param{Period} =~ /^(\d+):(\d+)/ ) {
|
|
$Period = $1 + $2 / 60;
|
|
}
|
|
|
|
# if start or end times are missing, delete the other entry
|
|
if ( !$Param{StartTime} || !$Param{EndTime} ) {
|
|
$Param{StartTime} = '';
|
|
$Param{EndTime} = '';
|
|
}
|
|
|
|
# add ':00' to the time, if it doesn't have it yet
|
|
if ( $Param{StartTime} && $Param{StartTime} !~ /^(\d+):(\d+)$/ ) {
|
|
$Param{StartTime} .= ':00';
|
|
}
|
|
if ( $Param{EndTime} && $Param{EndTime} !~ /^(\d+):(\d+)$/ ) {
|
|
$Param{EndTime} .= ':00';
|
|
}
|
|
if (
|
|
( $Param{StartTime} =~ /^(\d+):(\d+)$/ )
|
|
&& ( $Param{EndTime} =~ /^(\d+):(\d+)$/ )
|
|
)
|
|
{
|
|
$Param{StartTime} =~ /^(\d+):(\d+)$/;
|
|
my $StartTime = $1 * 60 + $2;
|
|
$Param{EndTime} =~ /^(\d+):(\d+)$/;
|
|
my $EndTime = $1 * 60 + $2;
|
|
if ( $ReduceTimeRef->{ $ActionList{ $Param{ActionID} } } ) {
|
|
$Period = ( $EndTime - $StartTime ) / 60
|
|
* $ReduceTimeRef->{ $ActionList{ $Param{ActionID} } } / 100;
|
|
}
|
|
else {
|
|
$Period = ( $EndTime - $StartTime ) / 60;
|
|
}
|
|
|
|
# end time must be after start time
|
|
if ( $EndTime <= $StartTime ) {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
push @EndTimeServerErrorBlock, 'EndTimeBeforeStartTimeServerError';
|
|
}
|
|
|
|
$StartTimes[$ID] = $StartTime;
|
|
$EndTimes[$ID] = $EndTime;
|
|
}
|
|
else {
|
|
if ( $Param{StartTime} && $Param{StartTime} !~ /^(\d+):(\d+)$/ ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
push @StartTimeServerErrorBlock, 'StartTimeInvalidFormatServerError';
|
|
}
|
|
if ( $Param{EndTime} && $Param{EndTime} !~ /^(\d+):(\d+)$/ ) {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
push @EndTimeServerErrorBlock, 'EndTimeInvalidFormatServerError';
|
|
}
|
|
}
|
|
|
|
# negative times are not allowed
|
|
if ( $Param{StartTime} =~ /^-(\d+):(\d+)$/ ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
push @StartTimeServerErrorBlock, 'StartTimeNegativeServerError';
|
|
}
|
|
if ( $Param{EndTime} =~ /^-(\d+):(\d+)$/ ) {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
push @EndTimeServerErrorBlock, 'EndTimeNegativeServerError';
|
|
}
|
|
|
|
# repeated hours are not allowed
|
|
POSITION:
|
|
for ( my $Position = $ID - 1; $Position >= 1; $Position-- ) {
|
|
next POSITION if !defined $StartTimes[$Position];
|
|
next POSITION if !defined $StartTimes[$ID];
|
|
|
|
if (
|
|
$StartTimes[$Position] > $StartTimes[$ID]
|
|
&& $StartTimes[$Position] < $EndTimes[$ID]
|
|
)
|
|
{
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^EndTimeRepeatedHourServerError/} @EndTimeServerErrorBlock ) {
|
|
push @EndTimeServerErrorBlock, 'EndTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
|
|
if (
|
|
$EndTimes[$Position] > $StartTimes[$ID]
|
|
&& $EndTimes[$Position] < $EndTimes[$ID]
|
|
)
|
|
{
|
|
if ( $EndTimes[$ID] > $EndTimes[$Position] ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
if (
|
|
!grep {/^StartTimeRepeatedHourServerError$/}
|
|
@StartTimeServerErrorBlock
|
|
)
|
|
{
|
|
push @StartTimeServerErrorBlock, 'StartTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
else {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^EndTimeRepeatedHourServerError$/} @EndTimeServerErrorBlock ) {
|
|
push @EndTimeServerErrorBlock, 'EndTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( $StartTimes[$Position] == $StartTimes[$ID] ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^StartTimeRepeatedHourServerError$/} @StartTimeServerErrorBlock ) {
|
|
push @StartTimeServerErrorBlock, 'StartTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
|
|
if ( $EndTimes[$Position] == $EndTimes[$ID] ) {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^EndTimeRepeatedHourServerError$/} @EndTimeServerErrorBlock ) {
|
|
push @EndTimeServerErrorBlock, 'EndTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
|
|
if (
|
|
$StartTimes[$ID] > $StartTimes[$Position]
|
|
&& $StartTimes[$ID] < $EndTimes[$Position]
|
|
)
|
|
{
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^StartTimeRepeatedHourServerError$/} @StartTimeServerErrorBlock ) {
|
|
push @StartTimeServerErrorBlock, 'StartTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
|
|
if (
|
|
$EndTimes[$ID] > $StartTimes[$Position]
|
|
&& $EndTimes[$ID] < $EndTimes[$Position]
|
|
)
|
|
{
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
if ( !grep {/^EndTimeRepeatedHourServerError$/} @EndTimeServerErrorBlock ) {
|
|
push @EndTimeServerErrorBlock, 'EndTimeRepeatedHourServerError';
|
|
}
|
|
}
|
|
}
|
|
|
|
# '24:00' is only permitted as end time
|
|
if ( $StartTimes[$ID] && $StartTimes[$ID] == 1440 ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
push @StartTimeServerErrorBlock, 'StartTime24Hours';
|
|
}
|
|
|
|
# times superior to 24:00 are not allowed
|
|
if ( $StartTimes[$ID] && $StartTimes[$ID] > 1440 ) {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
push @StartTimeServerErrorBlock, 'StartTimeInvalid';
|
|
}
|
|
if ( $EndTimes[$ID] && $EndTimes[$ID] > 1440 ) {
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
push @EndTimeServerErrorBlock, 'EndTimeInvalid';
|
|
}
|
|
|
|
# add reference to the server error messages to be shown
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{StartTimeInvalid} ) {
|
|
$Errors{$ErrorIndex}{StartTimeServerErrorBlock} = \@StartTimeServerErrorBlock;
|
|
}
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{EndTimeInvalid} ) {
|
|
$Errors{$ErrorIndex}{EndTimeServerErrorBlock} = \@EndTimeServerErrorBlock;
|
|
}
|
|
|
|
# overwrite period if there are start and end times
|
|
if ( $StartTimes[$ID] && $EndTimes[$ID] ) {
|
|
$Period = $EndTimes[$ID] - $StartTimes[$ID];
|
|
|
|
# convert period from minutes to hours
|
|
$Period /= 60;
|
|
}
|
|
|
|
# check for errors in the period
|
|
if ( $Period == 0 ) {
|
|
push @PeriodServerErrorBlock, 'ZeroHoursPeriodServerError';
|
|
if (
|
|
$ConfigObject->Get('TimeAccounting::InputHoursWithoutStartEndTime')
|
|
)
|
|
{
|
|
$Errors{$ErrorIndex}{PeriodInvalid} = 'ServerError';
|
|
}
|
|
else {
|
|
$Errors{$ErrorIndex}{StartTimeInvalid} = 'ServerError';
|
|
$Errors{$ErrorIndex}{EndTimeInvalid} = 'ServerError';
|
|
}
|
|
}
|
|
else {
|
|
if ( $Period < 0 ) {
|
|
$Errors{$ErrorIndex}{PeriodInvalid} = 'ServerError';
|
|
push @PeriodServerErrorBlock, 'NegativePeriodServerError';
|
|
}
|
|
}
|
|
if ( $Period > 24 ) {
|
|
$Errors{$ErrorIndex}{PeriodInvalid} = 'ServerError';
|
|
push @PeriodServerErrorBlock, 'InvalidHoursPeriodServerError';
|
|
}
|
|
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{PeriodInvalid} ) {
|
|
$Errors{$ErrorIndex}{PeriodServerErrorBlock} = \@PeriodServerErrorBlock;
|
|
}
|
|
|
|
# if there was an error on this row, save all data in the server error hash
|
|
if ( defined $Errors{$ErrorIndex} ) {
|
|
for my $Parameter (qw(ProjectID ActionID Remark StartTime EndTime)) {
|
|
$ServerErrorData{$ErrorIndex}{$Parameter} = $Param{$Parameter};
|
|
}
|
|
$ServerErrorData{$ErrorIndex}{Period} = $Period;
|
|
}
|
|
|
|
# otherwise, save row on the DB
|
|
else {
|
|
|
|
# initialize the array of working units
|
|
@{ $Data{WorkingUnits} } = ();
|
|
|
|
# replace 24:00 for 23:59:59 to be a valid entry in the DB
|
|
$Param{EndTime} = $Param{EndTime} eq '24:00' ? '23:59:59' : $Param{EndTime};
|
|
|
|
my %WorkingUnit = (
|
|
ProjectID => $Param{ProjectID},
|
|
ActionID => $Param{ActionID},
|
|
Remark => $Param{Remark},
|
|
StartTime => $Param{StartTime},
|
|
EndTime => $Param{EndTime},
|
|
Period => $Period,
|
|
);
|
|
push @{ $Data{WorkingUnits} }, \%WorkingUnit;
|
|
|
|
$Data{Year} = $Param{Year};
|
|
$Data{Month} = $Param{Month};
|
|
$Data{Day} = $Param{Day};
|
|
$Data{UserID} = $Self->{UserID};
|
|
|
|
if ( !$TimeAccountingObject->WorkingUnitsInsert(%Data) ) {
|
|
|
|
return $LayoutObject->ErrorScreen(
|
|
Message => Translatable('Can\'t insert Working Units!'),
|
|
);
|
|
}
|
|
$Param{SuccessfulInsert} = 1;
|
|
}
|
|
|
|
# increment the error index if there was an error on this row
|
|
$ErrorIndex++ if ( defined $Errors{$ErrorIndex} );
|
|
}
|
|
|
|
if (%ServerErrorData) {
|
|
$Param{SuccessfulInsert} = undef;
|
|
}
|
|
}
|
|
|
|
# Show Working Units
|
|
# get existing working units
|
|
%Data = $TimeAccountingObject->WorkingUnitsGet(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
# get number of working units (=records)
|
|
# if bigger than RecordsNumber, more than the number of default records were saved for
|
|
# this date
|
|
if ( $Data{WorkingUnits} ) {
|
|
my $WorkingUnitsCount = @{ $Data{WorkingUnits} };
|
|
if ( $WorkingUnitsCount > $Param{RecordsNumber} ) {
|
|
$Param{RecordsNumber} = $WorkingUnitsCount;
|
|
}
|
|
}
|
|
|
|
my %Frontend;
|
|
|
|
if ( $ConfigObject->Get('TimeAccounting::InputHoursWithoutStartEndTime') ) {
|
|
$Param{PeriodBlock} = 'UnitInputPeriod';
|
|
$Frontend{PeriodNote} = '*';
|
|
}
|
|
else {
|
|
$Param{PeriodBlock} = 'UnitPeriodWithoutInput';
|
|
$Frontend{PeriodNote} = '';
|
|
}
|
|
|
|
if ( $DateTimeObjectCurrent->Compare( DateTimeObject => $DateTimeObjectGiven ) ) {
|
|
$LayoutObject->Block(
|
|
Name => 'UnitBlock',
|
|
Data => { %Param, %Frontend },
|
|
);
|
|
}
|
|
|
|
# get sick, leave day and overtime
|
|
$Param{Sick} = $Data{Sick} ? 'checked="checked"' : '';
|
|
$Param{LeaveDay} = $Data{LeaveDay} ? 'checked="checked"' : '';
|
|
$Param{Overtime} = $Data{Overtime} ? 'checked="checked"' : '';
|
|
|
|
$Param{Total} = $Data{Total};
|
|
|
|
# set action list and related constraints
|
|
# generate a JavaScript Array which will be output to the template
|
|
my @ActionIDs = sort { $ActionList{$a} cmp $ActionList{$b} } keys %ActionList;
|
|
my @JSActions;
|
|
for my $ActionID (@ActionIDs) {
|
|
push @JSActions, [
|
|
$ActionID,
|
|
$ActionList{$ActionID}
|
|
];
|
|
}
|
|
|
|
$LayoutObject->AddJSData(
|
|
Key => 'ActionList',
|
|
Value => \@JSActions
|
|
);
|
|
|
|
my $ActionListConstraints = $ConfigObject->Get('TimeAccounting::ActionListConstraints');
|
|
my @JSActionListConstraints;
|
|
for my $ProjectNameRegExp ( sort keys %{$ActionListConstraints} ) {
|
|
my $ActionNameRegExp = $ActionListConstraints->{$ProjectNameRegExp};
|
|
s{(['"\\])}{\\$1}smxg for ( $ProjectNameRegExp, $ActionNameRegExp );
|
|
push @JSActionListConstraints, [
|
|
$ProjectNameRegExp,
|
|
$ActionNameRegExp
|
|
];
|
|
}
|
|
|
|
$LayoutObject->AddJSData(
|
|
Key => 'ActionListConstraints',
|
|
Value => \@JSActionListConstraints
|
|
);
|
|
|
|
# build a working unit array
|
|
my @Units = (undef);
|
|
if ( $Data{WorkingUnits} ) {
|
|
push @Units, @{ $Data{WorkingUnits} };
|
|
}
|
|
|
|
$ErrorIndex = 0;
|
|
|
|
# build units
|
|
for my $ID ( 1 .. $Param{RecordsNumber} ) {
|
|
$Param{ID} = $ID;
|
|
my $UnitRef = $Units[$ID];
|
|
my $ShowError = 0;
|
|
|
|
if ( !$UnitRef ) {
|
|
$ErrorIndex++;
|
|
$ShowError = 1;
|
|
}
|
|
|
|
# get data of projects
|
|
my $ProjectList = $Self->_ProjectList(
|
|
SelectedID => $UnitRef->{ProjectID}
|
|
|| $ServerErrorData{$ErrorIndex}{ProjectID}
|
|
|| '',
|
|
);
|
|
|
|
$Param{ProjectID} = $UnitRef->{ProjectID}
|
|
|| $ServerErrorData{$ErrorIndex}{ProjectID}
|
|
|| '';
|
|
$Param{ProjectName} = '';
|
|
|
|
# set common params for project selection
|
|
my %ProjectOptionParams = (
|
|
Name => "ProjectID[$ID]",
|
|
ID => "ProjectID$ID",
|
|
Translation => 0,
|
|
Class => 'Validate_TimeAccounting_Project ProjectSelection '
|
|
. ( $Errors{$ErrorIndex}{ProjectIDInvalid} || '' ),
|
|
OnChange => "TimeAccounting.Agent.EditTimeRecords.FillActionList($ID);",
|
|
Title => $LayoutObject->{LanguageObject}->Translate("Project"),
|
|
);
|
|
|
|
my $EnableAutoCompletion = $ConfigObject->Get("TimeAccounting::EnableAutoCompletion") || 0;
|
|
my $Class = $EnableAutoCompletion ? ' Modernize' : '';
|
|
|
|
# set params for modern inputs
|
|
$ProjectOptionParams{Class} .= $Class;
|
|
|
|
if ( $EnableAutoCompletion && $ConfigObject->Get("TimeAccounting::UseFilter") ) {
|
|
|
|
# add filter for the previous projects
|
|
$ProjectOptionParams{Data} = $ProjectList->{AllProjects};
|
|
$ProjectOptionParams{Filters} = {
|
|
LastProjects => {
|
|
Name => $LayoutObject->{LanguageObject}->Translate('Last Projects'),
|
|
Values => $ProjectList->{LastProjects},
|
|
},
|
|
};
|
|
|
|
# if there are the previous projects, expand filter dialog
|
|
if ( scalar @{ $ProjectList->{LastProjects} } > 1 ) {
|
|
$ProjectOptionParams{ExpandFilters} = 1;
|
|
|
|
# make the filter active by default if 'ActiveFilter' is enabled
|
|
if ( $ConfigObject->Get("TimeAccounting::ActiveFilter") ) {
|
|
$ProjectOptionParams{Filters}->{LastProjects}->{Active} = 1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# set params for traditional selects (and modern input fields if filter is not used)
|
|
my @Projects = ( @{ $ProjectList->{LastProjects} }, @{ $ProjectList->{AllProjects} } );
|
|
$ProjectOptionParams{Data} = \@Projects;
|
|
}
|
|
|
|
# build projects select
|
|
$Frontend{ProjectOption} = $LayoutObject->BuildSelection(
|
|
%ProjectOptionParams,
|
|
);
|
|
|
|
# action list initially only contains empty and selected element as well as elements
|
|
# configured for selected project
|
|
# if no constraints are configured, all actions will be displayed
|
|
my $ActionData = $Self->_ActionListConstraints(
|
|
ProjectID => $UnitRef->{ProjectID} || $ServerErrorData{$ErrorIndex}->{ProjectID},
|
|
ProjectList => $ProjectList,
|
|
ActionList => \%ActionList,
|
|
ActionListConstraints => $ActionListConstraints,
|
|
);
|
|
$ActionData->{''} = '-';
|
|
|
|
if ( $UnitRef && $UnitRef->{ActionID} && $ActionList{ $UnitRef->{ActionID} } ) {
|
|
$ActionData->{ $UnitRef->{ActionID} } = $ActionList{ $UnitRef->{ActionID} };
|
|
}
|
|
elsif (
|
|
$ServerErrorData{$ErrorIndex}
|
|
&& $ServerErrorData{$ErrorIndex}{ActionID}
|
|
&& $ActionList{ $ServerErrorData{$ErrorIndex}{ActionID} }
|
|
)
|
|
{
|
|
$ActionData->{ $ServerErrorData{$ErrorIndex}{ActionID} }
|
|
= $ActionList{ $ServerErrorData{$ErrorIndex}{ActionID} };
|
|
}
|
|
|
|
$Frontend{ActionOption} = $LayoutObject->BuildSelection(
|
|
|
|
Data => $ActionData,
|
|
SelectedID => $UnitRef->{ActionID} || $ServerErrorData{$ErrorIndex}{ActionID} || '',
|
|
Name => "ActionID[$ID]",
|
|
ID => "ActionID$ID",
|
|
Translation => 0,
|
|
Class => 'Validate_DependingRequiredAND Validate_Depending_ProjectID'
|
|
. $ID
|
|
. ' ActionSelection '
|
|
. ( $Errors{$ErrorIndex}{ActionIDInvalid} || '' )
|
|
. $Class,
|
|
Title => $LayoutObject->{LanguageObject}->Translate("Task"),
|
|
);
|
|
|
|
$Param{Remark} = $UnitRef->{Remark} || $ServerErrorData{$ErrorIndex}{Remark} || '';
|
|
|
|
my $Period;
|
|
if (
|
|
( $UnitRef->{Period} && $UnitRef->{Period} == 0 )
|
|
|| (
|
|
defined $ServerErrorData{$ErrorIndex}{Period}
|
|
&& $ServerErrorData{$ErrorIndex}{Period} == 0
|
|
)
|
|
)
|
|
{
|
|
$Period = 0;
|
|
}
|
|
else {
|
|
$Period = $UnitRef->{Period} || $ServerErrorData{$ErrorIndex}{Period} || '';
|
|
}
|
|
|
|
for my $TimePeriod (qw(StartTime EndTime)) {
|
|
if ($ShowError) {
|
|
$Param{$TimePeriod} = $ServerErrorData{$ErrorIndex}{$TimePeriod} // '';
|
|
}
|
|
else {
|
|
$Param{$TimePeriod} = $UnitRef->{$TimePeriod} // '';
|
|
}
|
|
}
|
|
|
|
if (
|
|
$Period
|
|
&& $Param{StartTime} eq '00:00'
|
|
&& $Param{EndTime} eq '00:00'
|
|
)
|
|
{
|
|
$Param{StartTime} = '';
|
|
$Param{EndTime} = '';
|
|
}
|
|
|
|
$LayoutObject->Block(
|
|
Name => 'Unit',
|
|
Data => {
|
|
%Param,
|
|
%Frontend,
|
|
%{ $Errors{$ErrorIndex} },
|
|
},
|
|
);
|
|
|
|
# add proper server error message for the start and end times
|
|
my $ServerErrorBlockName;
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{StartTimeInvalid} ) {
|
|
if ( scalar @{ $Errors{$ErrorIndex}{StartTimeServerErrorBlock} } > 0 ) {
|
|
while ( @{ $Errors{$ErrorIndex}{StartTimeServerErrorBlock} } ) {
|
|
$ServerErrorBlockName = shift @{ $Errors{$ErrorIndex}{StartTimeServerErrorBlock} };
|
|
$LayoutObject->Block(
|
|
Name => $ServerErrorBlockName,
|
|
Data => {},
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'StartTimeGenericServerError',
|
|
Data => {},
|
|
);
|
|
}
|
|
}
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{EndTimeInvalid} ) {
|
|
if ( scalar @{ $Errors{$ErrorIndex}{EndTimeServerErrorBlock} } > 0 ) {
|
|
while ( @{ $Errors{$ErrorIndex}{EndTimeServerErrorBlock} } ) {
|
|
$ServerErrorBlockName = shift @{ $Errors{$ErrorIndex}{EndTimeServerErrorBlock} };
|
|
$LayoutObject->Block(
|
|
Name => $ServerErrorBlockName,
|
|
Data => {}
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'EndTimeGenericServerError',
|
|
Data => {},
|
|
);
|
|
}
|
|
}
|
|
|
|
$LayoutObject->Block(
|
|
Name => $Param{PeriodBlock},
|
|
Data => {
|
|
Period => $Period,
|
|
ID => $ID,
|
|
%{ $Errors{$ErrorIndex} },
|
|
},
|
|
);
|
|
|
|
# add proper server error message for the period
|
|
if ( $Errors{$ErrorIndex} && $Errors{$ErrorIndex}{PeriodInvalid} ) {
|
|
if ( scalar @{ $Errors{$ErrorIndex}{PeriodServerErrorBlock} } > 0 ) {
|
|
while ( @{ $Errors{$ErrorIndex}{PeriodServerErrorBlock} } ) {
|
|
$ServerErrorBlockName = shift @{ $Errors{$ErrorIndex}{PeriodServerErrorBlock} };
|
|
$LayoutObject->Block(
|
|
Name => $ServerErrorBlockName,
|
|
Data => {},
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'PeriodGenericServerError',
|
|
Data => {},
|
|
);
|
|
}
|
|
}
|
|
else {
|
|
$LayoutObject->Block(
|
|
Name => 'PeriodGenericServerError',
|
|
Data => {},
|
|
);
|
|
}
|
|
|
|
# validity check
|
|
if (
|
|
$Param{InsertWorkingUnits}
|
|
&& $UnitRef->{ProjectID}
|
|
&& $UnitRef->{ActionID}
|
|
&& $Param{Sick}
|
|
)
|
|
{
|
|
$Param{BlockName} = $LayoutObject->{LanguageObject}
|
|
->Translate('Are you sure that you worked while you were on sick leave?');
|
|
}
|
|
elsif (
|
|
$Param{InsertWorkingUnits}
|
|
&& $UnitRef->{ProjectID}
|
|
&& $UnitRef->{ActionID}
|
|
&& $Param{LeaveDay}
|
|
)
|
|
{
|
|
$Param{BlockName} = $LayoutObject->{LanguageObject}
|
|
->Translate('Are you sure that you worked while you were on vacation?');
|
|
}
|
|
elsif (
|
|
$Param{InsertWorkingUnits}
|
|
&& $UnitRef->{ProjectID}
|
|
&& $UnitRef->{ActionID}
|
|
&& $Param{Overtime}
|
|
)
|
|
{
|
|
$Param{BlockName} = $LayoutObject->{LanguageObject}
|
|
->Translate('Are you sure that you worked while you were on overtime leave?');
|
|
}
|
|
}
|
|
|
|
if ( $DateTimeObjectCurrent->Compare( DateTimeObject => $DateTimeObjectGiven ) ) {
|
|
$Param{Total} = sprintf( "%.2f", ( $Param{Total} || 0 ) );
|
|
$LayoutObject->Block(
|
|
Name => 'Total',
|
|
Data => { %Param, %Frontend },
|
|
);
|
|
}
|
|
|
|
# validity checks start
|
|
my $ErrorNote;
|
|
if ( $Param{Total} && $Param{Total} > 24 ) {
|
|
$ErrorNote = Translatable('Can\'t save settings, because a day has only 24 hours!');
|
|
}
|
|
elsif ( $Param{InsertWorkingUnits} && $Param{Total} && $Param{Total} > 16 ) {
|
|
$Param{BlockName}
|
|
= $LayoutObject->{LanguageObject}->Translate('Are you sure that you worked more than 16 hours?');
|
|
}
|
|
if ($ErrorNote) {
|
|
if (
|
|
!$TimeAccountingObject->WorkingUnitsDelete(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
UserID => $Self->{UserID},
|
|
)
|
|
)
|
|
{
|
|
return $LayoutObject->ErrorScreen(
|
|
Message => Translatable('Can\'t delete Working Units!'),
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( $Param{BlockName} && $Param{SuccessfulInsert} ) {
|
|
$LayoutObject->AddJSData(
|
|
Key => 'BlockName',
|
|
Value => $Param{BlockName},
|
|
);
|
|
}
|
|
|
|
$Param{Date} = $LayoutObject->BuildDateSelection(
|
|
%Param,
|
|
Validate => 1,
|
|
Prefix => '',
|
|
Format => 'DateInputFormat',
|
|
);
|
|
|
|
if ( $DateTimeObjectGiven->Compare( DateTimeObject => $DateTimeObjectAllowed ) < 0 ) {
|
|
if (
|
|
$IncompleteWorkingDays{Incomplete}{ $Param{Year} }{ $Param{Month} }{ $Param{Day} }
|
|
&& !$Param{SuccessfulInsert}
|
|
)
|
|
{
|
|
$LayoutObject->Block(
|
|
Name => 'Readonly',
|
|
Data => {
|
|
Description =>
|
|
Translatable(
|
|
'This Date is out of limit, but you haven\'t insert this day yet, so you get one(!) chance to insert'
|
|
),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# get incomplete working days
|
|
my %IncompleteWorkingDaysList;
|
|
%IncompleteWorkingDays = $TimeAccountingObject->WorkingUnitsCompletnessCheck(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
for my $YearID ( sort keys %{ $IncompleteWorkingDays{Incomplete} } ) {
|
|
for my $MonthID ( sort keys %{ $IncompleteWorkingDays{Incomplete}{$YearID} } ) {
|
|
for my $DayID (
|
|
sort keys %{ $IncompleteWorkingDays{Incomplete}{$YearID}{$MonthID} }
|
|
)
|
|
{
|
|
$IncompleteWorkingDaysList{"$YearID-$MonthID-$DayID"} = "$YearID-$MonthID-$DayID";
|
|
$Param{Incomplete} = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Show text, if incomplete working days are available
|
|
if ( $Param{Incomplete} ) {
|
|
|
|
# if mass entry option is enabled, show list of working days
|
|
if ( $ConfigObject->Get("TimeAccounting::AllowMassEntryForUser") ) {
|
|
|
|
$LayoutObject->Block(
|
|
Name => 'IncompleteWorkingDaysMassEntry',
|
|
);
|
|
|
|
for my $WorkingDays ( sort keys %IncompleteWorkingDaysList ) {
|
|
|
|
my ( $Year, $Month, $Day ) = split( /-/, $IncompleteWorkingDaysList{$WorkingDays} );
|
|
my $DateTimeObjectWorkingDay = $Kernel::OM->Create(
|
|
'Kernel::System::DateTime',
|
|
ObjectParams => {
|
|
Year => $Year,
|
|
Month => $Month,
|
|
Day => $Day,
|
|
}
|
|
);
|
|
|
|
$LayoutObject->Block(
|
|
Name => 'IncompleteWorkingDaysMassEntrySingleDay',
|
|
Data => {
|
|
Date => $IncompleteWorkingDaysList{$WorkingDays},
|
|
DateHR => $DateTimeObjectWorkingDay->ToString(),
|
|
Weekday => $TimeAccountingObject->DayOfWeekToName(
|
|
Number => $TimeAccountingObject->DayOfWeek( $Year, $Month, $Day )
|
|
),
|
|
Year => $Year,
|
|
Month => $Month,
|
|
Day => $Day,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
# otherwise show incomplete working days as a drop-down
|
|
else {
|
|
|
|
# check if current day is incomplete
|
|
# if yes, select the current day in the drop-down
|
|
# otherwise select the empty element
|
|
my $SelectedID;
|
|
|
|
if ( $IncompleteWorkingDaysList{"$Param{Year}-$Param{Month}-$Param{Day}"} ) {
|
|
$SelectedID = "$Param{Year}-$Param{Month}-$Param{Day}";
|
|
}
|
|
|
|
my $IncompleWorkingDaysSelect = $LayoutObject->BuildSelection(
|
|
Data => \%IncompleteWorkingDaysList,
|
|
SelectedID => $SelectedID,
|
|
Name => "IncompleteWorkingDaysList",
|
|
PossibleNone => 1,
|
|
Title =>
|
|
$LayoutObject->{LanguageObject}->Translate("Incomplete Working Days"),
|
|
Class => 'Modernize',
|
|
);
|
|
|
|
$LayoutObject->Block(
|
|
Name => 'IncompleteWorkingDays',
|
|
Data => {
|
|
IncompleteWorkingDaysSelect => $IncompleWorkingDaysSelect,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
my %UserData = $TimeAccountingObject->UserGet(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
my $VacationCheck = $TimeAccountingObject->VacationCheck(
|
|
Year => $Param{Year},
|
|
Month => $Param{Month},
|
|
Day => $Param{Day},
|
|
Calendar => $UserData{Calendar},
|
|
);
|
|
|
|
$Param{Weekday}
|
|
= $Kernel::OM->Get('Kernel::System::TimeAccounting')->DayOfWeek( $Param{Year}, $Param{Month}, $Param{Day} );
|
|
|
|
# get working days of the user's calendar
|
|
my $CalendarName = 'TimeWorkingHours';
|
|
$CalendarName .= $UserData{Calendar} ? "::Calendar$UserData{Calendar}" : '';
|
|
my $CalendarWorkingHours = $ConfigObject->Get($CalendarName);
|
|
|
|
# show "other times" block, if necessary
|
|
if (
|
|
IsArrayRefWithData( $CalendarWorkingHours->{ $WeekdayArray[ $Param{Weekday} - 1 ] } )
|
|
&& !$VacationCheck
|
|
)
|
|
{
|
|
$LayoutObject->Block(
|
|
Name => 'OtherTimes',
|
|
Data => {
|
|
%Param,
|
|
%Frontend,
|
|
%Errors,
|
|
},
|
|
);
|
|
}
|
|
|
|
$Param{Weekday_to_Text} = $WeekdayArray[ $Param{Weekday} - 1 ];
|
|
|
|
# integrate the handling for required remarks in relation to projects
|
|
$Param{RemarkRegExp} = $Self->_Project2RemarkRegExp();
|
|
$LayoutObject->AddJSData(
|
|
Key => 'RemarkRegExp',
|
|
Value => $Param{RemarkRegExp},
|
|
);
|
|
|
|
# build output
|
|
my $Output = $LayoutObject->Header(
|
|
Title => 'Edit',
|
|
);
|
|
|
|
$Output .= $LayoutObject->NavigationBar();
|
|
$LayoutObject->Block(
|
|
Name => 'OverviewProject',
|
|
Data => { %Param, %Frontend },
|
|
);
|
|
|
|
if ( !$IncompleteWorkingDays{EnforceInsert} ) {
|
|
|
|
# show create project link, if allowed
|
|
my %UserData = $TimeAccountingObject->UserGet(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
if ( $UserData{CreateProject} ) {
|
|
$LayoutObject->Block(
|
|
Name => 'CreateProject',
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($ErrorNote) {
|
|
$Output .= $LayoutObject->Notify(
|
|
Info => $ErrorNote,
|
|
Priority => 'Error',
|
|
);
|
|
}
|
|
elsif ( defined $Param{SuccessfulInsert} )
|
|
{
|
|
$Output .= $LayoutObject->Notify(
|
|
Info => Translatable('Successful insert!'),
|
|
);
|
|
}
|
|
|
|
# show mass entry notification
|
|
if ( $Param{Notification} eq 'Error' ) {
|
|
$Output .= $LayoutObject->Notify(
|
|
Info => Translatable('Error while inserting multiple dates!'),
|
|
Priority => 'Error',
|
|
);
|
|
}
|
|
elsif ( $Param{Notification} eq 'Successful' ) {
|
|
$Output .= $LayoutObject->Notify(
|
|
Info => Translatable('Successfully inserted entries for several dates!'),
|
|
);
|
|
}
|
|
|
|
# show notification if wrong date was selected
|
|
if ( $Param{WrongDate} ) {
|
|
$Output .= $LayoutObject->Notify(
|
|
Info => Translatable('Entered date was invalid! Date was changed to today.'),
|
|
Priority => 'Error',
|
|
);
|
|
}
|
|
|
|
$LayoutObject->AddJSData(
|
|
Key => 'Year',
|
|
Value => $Param{Year},
|
|
);
|
|
$LayoutObject->AddJSData(
|
|
Key => 'Month',
|
|
Value => $Param{Month},
|
|
);
|
|
$LayoutObject->AddJSData(
|
|
Key => 'Day',
|
|
Value => $Param{Day},
|
|
);
|
|
|
|
$Output .= $LayoutObject->Output(
|
|
TemplateFile => 'AgentTimeAccountingEdit',
|
|
Data => {
|
|
%Param,
|
|
%Frontend,
|
|
},
|
|
);
|
|
$Output .= $LayoutObject->Footer();
|
|
return $Output;
|
|
|
|
}
|
|
|
|
sub _FirstUserRedirect {
|
|
my $Self = shift;
|
|
|
|
# For initial usage, the first agent with 'rw' rights will be redirected to 'Setting'. Then they can configure
|
|
# initial settings for the time accounting feature.
|
|
|
|
# Define action and get its frontend module registration.
|
|
my $Action = 'AgentTimeAccountingSetting';
|
|
my $Config = $Kernel::OM->Get('Kernel::Config')->Get('Frontend::Module')->{$Action};
|
|
|
|
# Get group names from config.
|
|
my @GroupNames = @{ $Config->{Group} || [] };
|
|
|
|
my $Permission = 0;
|
|
|
|
# If access is restricted, allow access only if user has appropriate permissions in configured group(s).
|
|
if (@GroupNames) {
|
|
|
|
my $GroupObject = $Kernel::OM->Get('Kernel::System::Group');
|
|
|
|
# Get user groups, where the user has the appropriate permissions.
|
|
my %Groups = $GroupObject->GroupMemberList(
|
|
UserID => $Self->{UserID},
|
|
Type => 'rw',
|
|
Result => 'HASH',
|
|
);
|
|
|
|
GROUP:
|
|
for my $GroupName (@GroupNames) {
|
|
next GROUP if !$GroupName;
|
|
|
|
# Get the group ID.
|
|
my $GroupID = $GroupObject->GroupLookup(
|
|
Group => $GroupName,
|
|
);
|
|
next GROUP if !$GroupID;
|
|
|
|
# Stop checking if membership in at least one group is found.
|
|
if ( $Groups{$GroupID} ) {
|
|
$Permission = 1;
|
|
last GROUP;
|
|
}
|
|
}
|
|
}
|
|
|
|
# Otherwise, always allow access.
|
|
else {
|
|
$Permission = 1;
|
|
}
|
|
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
|
|
return $LayoutObject->Redirect( OP => "Action=$Action" ) if $Permission;
|
|
|
|
return $LayoutObject->ErrorScreen(
|
|
Message =>
|
|
Translatable('No time period configured, or the specified date is outside of the defined time periods.'),
|
|
Comment => Translatable('Please contact the time accounting administrator to update your time periods!'),
|
|
);
|
|
}
|
|
|
|
sub _ActionList {
|
|
my $Self = shift;
|
|
|
|
my %ActionList;
|
|
my %Action = $Kernel::OM->Get('Kernel::System::TimeAccounting')->ActionSettingsGet();
|
|
|
|
# get action settings
|
|
ACTIONID:
|
|
for my $ActionID ( sort keys %Action ) {
|
|
next ACTIONID if !$Action{$ActionID}{ActionStatus};
|
|
next ACTIONID if !$Action{$ActionID}{Action};
|
|
$ActionList{$ActionID} = $Action{$ActionID}{Action};
|
|
}
|
|
$ActionList{''} = '';
|
|
|
|
return %ActionList;
|
|
}
|
|
|
|
sub _ActionListConstraints {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my %List;
|
|
if ( $Param{ProjectID} && keys %{ $Param{ActionListConstraints} } ) {
|
|
my $ProjectName;
|
|
|
|
PROJECT:
|
|
for my $Project ( @{ $Param{ProjectList}->{AllProjects} } ) {
|
|
if ( $Project->{Key} eq $Param{ProjectID} ) {
|
|
$ProjectName = $Project->{Value};
|
|
last PROJECT;
|
|
}
|
|
}
|
|
|
|
if ( defined($ProjectName) ) {
|
|
|
|
# loop over actions to find matches for configured project
|
|
# and action reg-exp pairs
|
|
for my $ActionID ( sort keys %{ $Param{ActionList} } ) {
|
|
|
|
my $ActionName = $Param{ActionList}->{$ActionID};
|
|
|
|
PROJECTNAMEREGEXP:
|
|
for my $ProjectNameRegExp ( sort keys %{ $Param{ActionListConstraints} } ) {
|
|
my $ActionNameRegExp = $Param{ActionListConstraints}->{$ProjectNameRegExp};
|
|
if (
|
|
$ProjectName =~ m{$ProjectNameRegExp}smx
|
|
&& $ActionName =~ m{$ActionNameRegExp}smx
|
|
)
|
|
{
|
|
$List{$ActionID} = $ActionName;
|
|
last PROJECTNAMEREGEXP;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# all available actions will be added if no action was added above (possible misconfiguration)
|
|
if ( !keys %List ) {
|
|
for my $ActionID ( sort keys %{ $Param{ActionList} } ) {
|
|
my $ActionName = $Param{ActionList}->{$ActionID};
|
|
$List{$ActionID} = $ActionName;
|
|
}
|
|
}
|
|
|
|
return \%List;
|
|
}
|
|
|
|
sub _ProjectList {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
# get time accounting object
|
|
my $TimeAccountingObject = $Kernel::OM->Get('Kernel::System::TimeAccounting');
|
|
|
|
# get project settings
|
|
my %Project = $TimeAccountingObject->ProjectSettingsGet(
|
|
Status => 'valid',
|
|
);
|
|
|
|
if ( !$Self->{LastProjectsRef} ) {
|
|
|
|
# get the last projects
|
|
my @LastProjects = $TimeAccountingObject->LastProjectsOfUser(
|
|
UserID => $Self->{UserID},
|
|
);
|
|
|
|
# add the favorites
|
|
%{ $Self->{LastProjectsRef} } = map { $_ => 1 } @LastProjects;
|
|
}
|
|
|
|
my @LastProjects = (
|
|
{
|
|
Key => '',
|
|
Value => '-',
|
|
},
|
|
);
|
|
|
|
# add the separator
|
|
PROJECTID:
|
|
for my $ProjectID (
|
|
sort { $Project{Project}{$a} cmp $Project{Project}{$b} }
|
|
keys %{ $Project{Project} }
|
|
)
|
|
{
|
|
next PROJECTID if !$Self->{LastProjectsRef}->{$ProjectID};
|
|
my %Hash = (
|
|
Key => $ProjectID,
|
|
Value => $Project{Project}{$ProjectID},
|
|
);
|
|
push @LastProjects, \%Hash;
|
|
}
|
|
|
|
@LastProjects = $Self->_ProjectListConstraints(
|
|
List => \@LastProjects,
|
|
SelectedID => $Param{SelectedID} || '',
|
|
);
|
|
|
|
my @AllProjects;
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
# check if AutoCompletion is disabled
|
|
# in this case a separator is needed between two lists of projects (last and all)
|
|
if (
|
|
!$ConfigObject->Get("TimeAccounting::EnableAutoCompletion")
|
|
|| !$ConfigObject->Get("TimeAccounting::UseFilter")
|
|
)
|
|
{
|
|
if ( scalar @LastProjects > 1 ) {
|
|
|
|
my $LayoutObject = $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
|
|
|
# add last projects separator to the beginning of the list
|
|
my $LastProjectsStr = $LayoutObject->{LanguageObject}->Translate('Last Selected Projects');
|
|
|
|
unshift @LastProjects, {
|
|
Key => '0',
|
|
Value => "---$LastProjectsStr---",
|
|
Disabled => 1,
|
|
};
|
|
|
|
# add all projects separator right after the last selected projects list
|
|
my $AllProjectsStr = $LayoutObject->{LanguageObject}->Translate('All Projects');
|
|
|
|
push @LastProjects, {
|
|
Key => '0',
|
|
Value => "---$AllProjectsStr---",
|
|
Disabled => 1,
|
|
};
|
|
}
|
|
}
|
|
else {
|
|
@AllProjects = (
|
|
{
|
|
Key => '',
|
|
Value => '-',
|
|
},
|
|
);
|
|
}
|
|
|
|
# add all allowed projects to the list
|
|
PROJECTID:
|
|
for my $ProjectID (
|
|
sort { $Project{Project}{$a} cmp $Project{Project}{$b} }
|
|
keys %{ $Project{Project} }
|
|
)
|
|
{
|
|
next PROJECTID if !$Project{Project}{$ProjectID};
|
|
my %Hash = (
|
|
Key => $ProjectID,
|
|
Value => $Project{Project}{$ProjectID},
|
|
);
|
|
if ( $Param{SelectedID} && $Param{SelectedID} eq $ProjectID ) {
|
|
$Hash{Selected} = 1;
|
|
}
|
|
|
|
push @AllProjects, \%Hash;
|
|
}
|
|
|
|
@AllProjects = $Self->_ProjectListConstraints(
|
|
List => \@AllProjects,
|
|
SelectedID => $Param{SelectedID} || '',
|
|
);
|
|
|
|
my %Projects = (
|
|
LastProjects => \@LastProjects,
|
|
AllProjects => \@AllProjects,
|
|
);
|
|
|
|
return \%Projects;
|
|
}
|
|
|
|
sub _ProjectListConstraints {
|
|
my ( $Self, %Param ) = @_;
|
|
|
|
my @List;
|
|
my $ProjectCount = 0;
|
|
my $ProjectListConstraints = $Kernel::OM->Get('Kernel::Config')->Get('TimeAccounting::ProjectListConstraints');
|
|
|
|
if ( keys %{$ProjectListConstraints} ) {
|
|
|
|
# get groups of current user
|
|
my %Groups = $Kernel::OM->Get('Kernel::System::Group')->GroupMemberList(
|
|
UserID => $Self->{UserID},
|
|
Type => 'ro',
|
|
Result => 'HASH',
|
|
);
|
|
%Groups = map { $Groups{$_} => 1 } keys %Groups;
|
|
|
|
# get project list constraints
|
|
my %ProjectRegex;
|
|
for my $ProjectRegex ( sort keys %{$ProjectListConstraints} ) {
|
|
for my $ProjectGroup ( split /,\s*/, $ProjectListConstraints->{$ProjectRegex} ) {
|
|
if ( $Groups{$ProjectGroup} ) {
|
|
$ProjectRegex{$ProjectRegex} = 1;
|
|
}
|
|
}
|
|
}
|
|
my @ProjectRegex = keys %ProjectRegex;
|
|
|
|
# reduce project list according to configuration
|
|
if ( ref( $Param{List} ) && @ProjectRegex ) {
|
|
|
|
my $ElementCount = 0;
|
|
|
|
for my $Project ( @{ $Param{List} } ) {
|
|
my $ProjectName = $Project->{Value};
|
|
|
|
# empty first element, last projects separator and currently selected project
|
|
if ( !$ElementCount || !$Project->{Key} || $Project->{Key} eq $Param{SelectedID} ) {
|
|
push @List, $Project;
|
|
}
|
|
else {
|
|
PROJECTREGEXP:
|
|
for my $ProjectRegex (@ProjectRegex) {
|
|
if ( $ProjectName =~ m{$ProjectRegex}smx ) {
|
|
push @List, $Project;
|
|
$ProjectCount++;
|
|
last PROJECTREGEXP;
|
|
}
|
|
}
|
|
}
|
|
$ElementCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
# get full project list if constraints resulted in empty project list or if constraints aren't
|
|
# configured (possible misconfiguration)
|
|
if ( !$ProjectCount ) {
|
|
@List = @{ $Param{List} };
|
|
}
|
|
|
|
return @List;
|
|
}
|
|
|
|
# integrate the handling for required remarks in relation to projects
|
|
|
|
sub _Project2RemarkRegExp {
|
|
my $Self = shift;
|
|
|
|
my @Projects2Remark = ();
|
|
my %ProjectData = $Kernel::OM->Get('Kernel::System::TimeAccounting')->ProjectSettingsGet(
|
|
Status => 'valid',
|
|
);
|
|
|
|
# get config object
|
|
my $ConfigObject = $Kernel::OM->Get('Kernel::Config');
|
|
|
|
return '' if !$ConfigObject->Get('TimeAccounting::Project2RemarkRegExp');
|
|
|
|
my $Project2RemarkRegExp = $ConfigObject->Get('TimeAccounting::Project2RemarkRegExp');
|
|
|
|
for my $ProjectID ( sort keys %{ $ProjectData{Project} } ) {
|
|
if ( $ProjectData{Project}{$ProjectID} =~ m{$Project2RemarkRegExp}smx ) {
|
|
push @Projects2Remark, $ProjectID;
|
|
}
|
|
}
|
|
|
|
return join '|', @Projects2Remark;
|
|
}
|
|
|
|
1;
|