The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
package Business::SLA;

use strict;
use warnings;

use vars qw($VERSION);
$VERSION = '0.05';

=head1 NAME

Business::SLA - 

=head1 SYNOPSIS

  use Business::SLA;

  my $SLAObj = Business::SLA->new(
    BusinessHours     => new Business::Hours,
    InHoursDefault    => '2 real hours',
    OutOfHoursDefault => '1 business hour',
  );

  # or set/change options later
  $SLAObj->SetBusinessHours( new Business::Hours );
  $SLAObj->SetInHoursDefault('2 real hours');
  $SLAObj->SetOutOfHoursDefault('1 business hour');

  # add service levels
  $SLAObj->Add( '2 real hours' => RealMinutes => 2*60 );
  $SLAObj->Add( '1 business hour' => BusinessMinutes => 60 );
  $SLAObj->Add( 'next business minute' );

=head1 DESCRIPTION

This module is a simple tool for handling operations related to
Service Level Agreements.

=head1 METHODS

=head2 new

Creates and returns new Business::SLA object.

Takes a hash with values of L</BusinessHours>, L</InHoursDefault>
and L</OutOfHoursDefault> options. You can ommit these options
and set them latter using methods (see below).

=cut

sub new {
    my $class = shift;

    my $self = bless( { @_ }, ref($class) || $class );

    return ($self);
}

=head2 SetBusinessHours

Sets a L<Business::Hours> object to use for calculations.
This module works without this option, but looses most functionality
you can get with it.

It's possible use any object that API-compatible with L<Business::Hours>.

=cut

sub SetBusinessHours {
    my $self     = shift;
    my $bizhours = shift;

    return $self->{'BusinessHours'} = $bizhours;
}

=head2 BusinessHours

Returns the current L<Business::Hours> object or undef if
it's not set.

=cut

sub BusinessHours {
    my $self = shift;

    return $self->{'BusinessHours'};
}

=head2 SetInHoursDefault

Sets the default service level for times inside of business hours.

Takes a service level.

=cut

sub SetInHoursDefault {
    my $self = shift;
    my $sla  = shift;

    return $self->{'InHoursDefault'} = $sla;
}

=head2 InHoursDefault

Returns the default service level for times inside of business hours.

=cut

sub InHoursDefault {
    my $self = shift;

    return $self->{'InHoursDefault'};
}

=head2 SetOutOfHoursDefault

Sets the default service level for times outside of business hours.

Takes a service level.

Note that L</BusinessHours> are used for calculations, so this
option makes not much sense without L<business hours have been
set|/SetBusinessHours>.

=cut

sub SetOutOfHoursDefault {
    my $self = shift;
    my $sla  = shift;

    $self->{'OutOfHoursDefault'} = $sla;
}

=head2 OutOfHoursDefault

Returns the default service level for times outside of business hours.

=cut

sub OutOfHoursDefault {
    my $self = shift;

    return $self->{'OutOfHoursDefault'};
}

=head2 IsInHours

Returns true if the date passed in is in business hours, and false otherwise.
If no L<business hours have been set|/SetBusinessHours>, returns true by default.

Takes a date in Unix time format (number of seconds since the epoch).

=cut

sub IsInHours {
    my $self = shift;
    my $date = shift;

    # if no business hours are set, by definition we're in hours
    if ( my $bhours = $self->BusinessHours ) {
        return $bhours->first_after($date) == $date? 1 : 0;
    }
    return 1;
}

=head2 SLA

Returns the default servise level for the specified time.

Takes a date in Unix time format (number of seconds since the epoch).

=cut

sub SLA {
    my $self = shift;
    my $date = shift;

    if ( $self->IsInHours($date) ) {
        return $self->InHoursDefault;
    }
    else {
        return $self->OutOfHoursDefault;
    }
}

=head2 Add

Adds or replaces a service level definition.

Takes a service level and a hash with agreements. In the hash you
can define BusinessMinutes, RealMinutes and StartImmediately boolean
option.

=cut

sub Add {
    my $self = shift;
    my $sla  = shift;

    return $self->{'hash'}->{$sla} = { @_ };
}

=head2 AddRealMinutes

The number of real minutes to add for the specified SLA.

Takes a service level.

=cut

sub AddRealMinutes {
    my $self = shift;
    my $sla  = shift;

    return 0 unless exists $self->{'hash'}{ $sla }{'RealMinutes'};
    return $self->{'hash'}{ $sla }{'RealMinutes'} || 0;
}

=head2 AddBusinessMinutes

The number of business minutes to add for the specified SLA.

Takes a service level.

=cut

sub AddBusinessMinutes {
    my $self = shift;
    my $sla  = shift;

    return undef unless $self->BusinessHours;
    return 0 unless exists $self->{'hash'}{ $sla }{'BusinessMinutes'};
    return $self->{'hash'}{ $sla }{'BusinessMinutes'} || 0;
}

=head2 StartImmediately

Returns true if things should be started immediately for a service
level. See also L<Add> and L</Starts>.

Takes the service level.

=cut

sub StartImmediately {
    my $self = shift;
    my $sla  = shift;

    return $self->{'hash'}{ $sla }{'StartImmediately'} || 0;
}

=head2 Starts

Returns the starting time, given a date and a service level.

If the service level's been defined as L<StartImmediately> then returns
the same date, as well this also happens if L<business hours are
not set|/SetBusinessHours>.

Takes a date in Unix time format (number of seconds since the epoch)
and a service level.

=cut

sub Starts {
    my $self = shift;
    my $date = shift;
    my $sla  = shift || $self->SLA( $date );

    return $date if $self->StartImmediately( $sla );

    if ( my $bhours = $self->BusinessHours ) {
        return $bhours->first_after( $date );
    }
    else {
        return $date;
    }
}

=head2 Due

Returns the due time, given an SLA and a date.

Takes a date in Unix time format (number of seconds since the epoch)
and the hash key for the SLA.

=cut

sub Due {
    my $self = shift;
    my $date = shift;
    my $sla  = shift || $self->SLA( $date );

    # find start time
    my $due = $self->Starts($date, $sla);

    # don't add business minutes unless we have some set
    if ( my $bminutes = $self->AddBusinessMinutes($sla) ) {
        $due = $self->BusinessHours->add_seconds( $due, 60 * $bminutes );
    }

    $due += ( 60 * $self->AddRealMinutes($sla) );

    return $due;
}

=head1 SUPPORT

Send email to bug-business-sla@rt.cpan.org

=head1 AUTHOR

    Linda Julien
    Best Practical Solutions, LLC 
    leira@bestpractical.com
    http://www.bestpractical.com

=head1 COPYRIGHT

This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the
LICENSE file included with this module.


=head1 SEE ALSO

perl(1), L<Business::Hours>.

=cut


1;