From: Moritz Bunkus Date: Fri, 8 Mar 2013 16:47:14 +0000 (+0100) Subject: AttrDuration-Helfer X-Git-Tag: release-3.1.0beta1~537 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=4180aaea33e9ff3bb35f3fa6cf91651a6225f7ad;p=kivitendo-erp.git AttrDuration-Helfer --- diff --git a/SL/DB/Helper/AttrDuration.pm b/SL/DB/Helper/AttrDuration.pm new file mode 100644 index 000000000..704c340ae --- /dev/null +++ b/SL/DB/Helper/AttrDuration.pm @@ -0,0 +1,182 @@ +package SL::DB::Helper::AttrDuration; + +use strict; + +use parent qw(Exporter); +our @EXPORT = qw(attr_duration); + +use Carp; + +sub attr_duration { + my ($package, @attributes) = @_; + + _make($package, $_) for @attributes; +} + +sub _make { + my ($package, $attribute) = @_; + + no strict 'refs'; + + *{ $package . '::' . $attribute . '_as_hours' } = sub { + my ($self, $value) = @_; + + $self->$attribute(int($value) + ($self->$attribute - int($self->$attribute))) if @_ > 1; + return int($self->$attribute // 0); + }; + + *{ $package . '::' . $attribute . '_as_minutes' } = sub { + my ($self, $value) = @_; + + $self->$attribute(int($self->$attribute) * 1.0 + ($value // 0) / 60.0) if @_ > 1; + return int(($self->$attribute // 0) * 60.0 + 0.5) % 60; + }; + + *{ $package . '::' . $attribute . '_as_duration_string' } = sub { + my ($self, $value) = @_; + + $self->$attribute(defined($value) ? $::form->parse_amount(\%::myconfig, $value) * 1 : undef) if @_ > 1; + return defined($self->$attribute) ? $::form->format_amount(\%::myconfig, $self->$attribute // 0, 2) : undef; + }; + + *{ $package . '::' . $attribute . '_as_man_days' } = sub { + my ($self, $value) = @_; + + if (@_ > 1) { + return undef if !defined $value; + $self->$attribute($value); + } + $value = $self->$attribute // 0; + return $value >= 8.0 ? $value / 8.0 : $value; + }; + + *{ $package . '::' . $attribute . '_as_man_days_unit' } = sub { + my ($self, $unit) = @_; + + if (@_ > 1) { + return undef if !defined $unit; + croak "Unknown unit '${unit}'" if $unit !~ m/^(?:h|hour|man_day)$/; + $self->$attribute(($self->$attribute // 0) * 8.0) if $unit eq 'man_day'; + } + + return ($self->$attribute // 0) >= 8.0 ? 'man_day' : 'h' + }; + + *{ $package . '::' . $attribute . '_as_man_days_string' } = sub { + my ($self, $value) = @_; + my $method = "${attribute}_as_man_days"; + + if (@_ > 1) { + return undef if !defined $value; + $self->$method($::form->parse_amount(\%::myconfig, $value)); + } + + return $::form->format_amount(\%::myconfig, $self->$method // 0, 2); + }; +} + +1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::DB::Helper::AttrDuration - Attribute helper for duration stored in +numeric columns + +=head1 SYNOPSIS + + # In a Rose model: + use SL::DB::Helper::AttrDuration; + __PACKAGE__->attr_duration('time_estimation'); + + # Read access: + print "Minutes: " . $obj->time_estimation_as_minutes . " hours: " . $obj->time_estimation_as_hours . "\n"; + + # Use formatted strings in input fields in templates: +
+ ... + [% L.input_tag('time_estimation_as_duration_string', SELF.obj.time_estimation_as_duration_string) %] +
+ +=head1 OVERVIEW + +This is a helper for columns that store a duration as a numeric or +floating point number representing a number of hours. So the value +1.75 would stand for "1 hour, 45 minutes". + +The helper methods created are: + +=over 4 + +=item C + +Access only the minutes. Return values are in the range [0 - 59]. + +=item C + +Access only the hours. Returns an integer value. + +=item C + +Access the full value as a formatted string according to the user's +locale settings. + +=item C + +Access the attribute as a number of man days which are assumed to be 8 +hours long. If the underlying attribute is less than 8 then the value +itself will be returned. Otherwise the value divided by 8 is returned. + +If used as a setter then the underlying attribute is simply set to + C<$new_value>. Intentional use is to set the man days first and the + unit later, e.g. + + $obj->attribute_as_man_days($::form->{attribute_as_man_days}); + $obj->attribute_as_man_days_unit($::form->{attribute_as_man_days_unit}); + +Note that L is aware of this and +handles this case correctly. + +=item C + +Returns the unit that the number returned by L +represents. This can be either C if the underlying attribute is +less than 8 and C otherwise. + +If used as a setter then the underlying attribute is multiplied by 8 +if C<$new_unit> equals C. Otherwise the underlying attribute +is not modified. Intentional use is to set the man days first and the +unit later, e.g. + + $obj->attribute_as_man_days($::form->{attribute_as_man_days}); + $obj->attribute_as_man_days_unit($::form->{attribute_as_man_days_unit}); + +Note that L is aware of this and +handles this case correctly. + +=back + +=head1 FUNCTIONS + +=over 4 + +=item C + +Package method. Call with the names of attributes for which the helper +methods should be created. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus Em.bunkus@linet-services.deE + +=cut diff --git a/SL/DB/Object.pm b/SL/DB/Object.pm index fc1365213..3cf670d55 100755 --- a/SL/DB/Object.pm +++ b/SL/DB/Object.pm @@ -60,6 +60,14 @@ sub _assign_attributes { my %types = map { $_->name => $_->type } ref($self)->meta->columns; + # Special case for *_as_man_days/*_as_man_days_unit: the _unit + # variation must always be called after the non-unit method. + my @man_days_attributes = grep { m/_as_man_days$/ } keys %attributes; + foreach my $attribute (@man_days_attributes) { + my $value = delete $attributes{$attribute}; + $self->$attribute(defined($value) && ($value eq '') ? undef : $value); + } + while (my ($attribute, $value) = each %attributes) { my $type = lc($types{$attribute} || 'text'); $value = $type eq 'boolean' ? ($value ? 't' : 'f') diff --git a/t/db_helper/attr_duration.t b/t/db_helper/attr_duration.t new file mode 100644 index 000000000..3af729687 --- /dev/null +++ b/t/db_helper/attr_duration.t @@ -0,0 +1,156 @@ +package AttrDurationTestDummy; + +use base qw(SL::DB::Object); + +__PACKAGE__->meta->setup( + table => 'dummy', + columns => [ dummy => { type => 'numeric', precision => 2, scale => 12 }, ] +); + +use SL::DB::Helper::AttrDuration; + +__PACKAGE__->attr_duration('dummy'); + +package main; + +use Test::More tests => 83; +use Test::Exception; + +use strict; + +use lib 't'; +use utf8; + +use Data::Dumper; +use Support::TestSetup; + +sub new_item { + return AttrDurationTestDummy->new(@_); +} + +Support::TestSetup::login(); +my $item; + +# Wenn das Attribut undef ist: +is(new_item->dummy, undef, 'uninitialized: raw'); +is(new_item->dummy_as_hours, 0, 'uninitialized: as_hours'); +is(new_item->dummy_as_minutes, 0, 'uninitialized: as_minutes'); +is(new_item->dummy_as_duration_string, undef, 'uninitialized: as_duration_string'); +is(new_item->dummy_as_man_days, 0, 'uninitialized: as_man_days'); +is(new_item->dummy_as_man_days_unit, 'h', 'uninitialized: as_man_days_unit'); +is(new_item->dummy_as_man_days_string, '0,00', 'uninitialized: as_man_days_string'); + +# Auslesen kleiner 8 Stunden: +is(new_item(dummy => 2.75)->dummy, 2.75, 'initialized < 8: raw'); +is(new_item(dummy => 2.75)->dummy_as_hours, 2, 'initialized < 8: as_hours'); +is(new_item(dummy => 2.75)->dummy_as_minutes, 45, 'initialized < 8: as_minutes'); +is(new_item(dummy => 2.75)->dummy_as_duration_string, '2,75', 'initialized < 8: as_duration_string'); +is(new_item(dummy => 2.75)->dummy_as_man_days, 2.75, 'initialized < 8: as_man_days'); +is(new_item(dummy => 2.75)->dummy_as_man_days_unit, 'h', 'initialized < 8: as_man_days_unit'); +is(new_item(dummy => 2.75)->dummy_as_man_days_string, '2,75', 'initialized < 8: as_man_days_string'); + +# Auslesen größer 8 Stunden: +is(new_item(dummy => 12.5)->dummy, 12.5, 'initialized > 8: raw'); +is(new_item(dummy => 12.5)->dummy_as_hours, 12, 'initialized > 8: as_hours'); +is(new_item(dummy => 12.5)->dummy_as_minutes, 30, 'initialized > 8: as_minutes'); +is(new_item(dummy => 12.5)->dummy_as_duration_string, '12,50', 'initialized > 8: as_duration_string'); +is(new_item(dummy => 12.5)->dummy_as_man_days, 1.5625, 'initialized > 8: as_man_days'); +is(new_item(dummy => 12.5)->dummy_as_man_days_unit, 'man_day', 'initialized > 8: as_man_days_unit'); +is(new_item(dummy => 12.5)->dummy_as_man_days_string, '1,56', 'initialized > 8: as_man_days_string'); + +$item = new_item(dummy => 2.25); $item->dummy_as_duration_string(undef); +is($item->dummy, undef, 'write as_duration_string undef read raw'); +is($item->dummy_as_minutes, 0, 'write as_duration_string undef read as_minutes'); +is($item->dummy_as_hours, 0, 'write as_duration_string undef read as_hours'); +is($item->dummy_as_duration_string, undef, 'write as_duration_string undef read as_duration_string'); + +$item = new_item(dummy => 2.25); $item->dummy_as_duration_string("4,80"); +is($item->dummy, 4.8, 'write as_duration_string 4,80 read raw'); +is($item->dummy_as_minutes, 48, 'write as_duration_string 4,80 read as_minutes'); +is($item->dummy_as_hours, 4, 'write as_duration_string 4,80 read as_hours'); +is($item->dummy_as_duration_string, "4,80", 'write as_duration_string 4,80 read as_duration_string'); + +$item = new_item(dummy => 2.25); $item->dummy_as_minutes(12); +is($item->dummy, 2.2, 'write as_minutes 12 read raw'); +is($item->dummy_as_minutes, 12, 'write as_minutes 12 read as_minutes'); +is($item->dummy_as_hours, 2, 'write as_minutes 12 read as_hours'); +is($item->dummy_as_duration_string, "2,20", 'write as_minutes 12 read as_duration_string'); + +$item = new_item(dummy => 2.25); $item->dummy_as_hours(5); +is($item->dummy, 5.25, 'write as_hours 5 read raw'); +is($item->dummy_as_minutes, 15, 'write as_hours 5 read as_minutes'); +is($item->dummy_as_hours, 5, 'write as_hours 5 read as_hours'); +is($item->dummy_as_duration_string, "5,25", 'write as_hours 5 read as_duration_string'); + +$item = new_item(dummy => undef); +is($item->dummy, undef, 'write raw undef read raw'); +is($item->dummy_as_man_days, 0, 'write raw undef read as_man_days'); +is($item->dummy_as_man_days_unit, 'h', 'write raw undef read as_man_days_unit'); +is($item->dummy_as_man_days_string, '0,00', 'write raw undef read as_man_days_string'); + +$item = new_item(dummy => 4); +is($item->dummy, 4, 'write raw 4 read raw'); +is($item->dummy_as_man_days, 4, 'write raw 4 read as_man_days'); +is($item->dummy_as_man_days_unit, 'h', 'write raw 4 read as_man_days_unit'); +is($item->dummy_as_man_days_string, '4,00', 'write raw 4 read as_man_days_string'); + +$item = new_item(dummy => 18); +is($item->dummy, 18, 'write raw 18 read raw'); +is($item->dummy_as_man_days, 2.25, 'write raw 18 read as_man_days'); +is($item->dummy_as_man_days_unit, 'man_day', 'write raw 18 read as_man_days_unit'); +is($item->dummy_as_man_days_string, '2,25', 'write raw 18 read as_man_days_string'); + +$item = new_item(dummy => 4); +is($item->dummy, 4, 'should not change anything when writing undef: write raw 4 read raw'); +is($item->dummy_as_man_days(undef), undef, 'should not change anything when writing undef: write as_man_days undef return undef'); +is($item->dummy, 4, 'should not change anything when writing undef: read raw 2'); +is($item->dummy_as_man_days_unit(undef), undef, 'should not change anything when writing undef: write as_man_days_unit undef return undef'); +is($item->dummy, 4, 'should not change anything when writing undef: read raw 3'); +is($item->dummy_as_man_days_string(undef), undef, 'should not change anything when writing undef: write as_man_days_string undef return undef'); +is($item->dummy, 4, 'should not change anything when writing undef: read raw 4'); + + +$item = new_item; +is($item->dummy(2), 2, 'parse less than a man day: write raw 2 read raw'); +is($item->dummy_as_man_days(0.75), 0.75, 'parse less than a man day: write as_man_days 0.75 read as_man_days'); +is($item->dummy_as_man_days_string('0,5'), '0,50', 'parse less than a man day: write as_man_days_string 0,5 read read as_man_days_string'); + +$item = new_item; +is($item->dummy(12), 12, 'parse more than a man day: write raw 12 read raw'); +is($item->dummy_as_man_days(13.25), 1.65625, 'parse more than a man day: write as_man_days 13.25 read as_man_days'); +is($item->dummy_as_man_days_string('13,5'), '1,69', 'parse more than a man day: write as_man_days_string 13,5 read read as_man_days_string'); + +$item = new_item; +is($item->dummy(3.25), 3.25, 'parse less than a man day with unit h: write raw 3.25 read raw'); +is($item->dummy_as_man_days_unit('h'), 'h', 'parse less than a man day with unit h: write as_man_days_unit h read as_man_days_unit'); +is($item->dummy, 3.25, 'parse less than a man day with unit h: read raw'); + +$item = new_item; +is($item->dummy(3.25), 3.25, 'parse less than a man day with unit hour: write raw 3.25 read raw'); +is($item->dummy_as_man_days_unit('hour'), 'h', 'parse less than a man day with unit hour: write as_man_days_unit hour read as_man_days_unit'); +is($item->dummy, 3.25, 'parse less than a man day with unit hour: read raw'); + +$item = new_item; +is($item->dummy(3.25), 3.25, 'parse more than a man day with unit man_day: write raw 3.25 read raw'); +is($item->dummy_as_man_days_unit('man_day'), 'man_day', 'parse more than a man day with unit man_day: write as_man_days_unit man_day read as_man_days_unit'); +is($item->dummy, 26, 'parse more than a man day with unit man_day: read raw'); + +is(new_item->assign_attributes(dummy_as_man_days => 3, dummy_as_man_days_unit => 'h')->dummy, 3, 'assign_attributes hash 3h'); +is(new_item->assign_attributes(dummy_as_man_days_unit => 'h', dummy_as_man_days => 3 )->dummy, 3, 'assign_attributes hash h3'); + +is(new_item->assign_attributes(dummy_as_man_days => 3, dummy_as_man_days_unit => 'man_day')->dummy, 24, 'assign_attributes hash 3man_day'); +is(new_item->assign_attributes(dummy_as_man_days_unit => 'man_day', dummy_as_man_days => 3 )->dummy, 24, 'assign_attributes hash man_day3'); + +is(new_item->assign_attributes('dummy_as_man_days', 3, 'dummy_as_man_days_unit', 'h')->dummy, 3, 'assign_attributes array 3h'); +is(new_item->assign_attributes('dummy_as_man_days_unit', 'h', 'dummy_as_man_days', 3 )->dummy, 3, 'assign_attributes array h3'); + +is(new_item->assign_attributes('dummy_as_man_days', 3, 'dummy_as_man_days_unit', 'man_day')->dummy, 24, 'assign_attributes array 3man_day'); +is(new_item->assign_attributes('dummy_as_man_days_unit', 'man_day', 'dummy_as_man_days', 3 )->dummy, 24, 'assign_attributes array man_day3'); + +# Parametervalidierung +throws_ok { new_item()->dummy_as_man_days_unit('invalid') } qr/unknown.*unit/i, 'unknown unit'; +lives_ok { new_item()->dummy_as_man_days_unit('h') } 'known unit h'; +lives_ok { new_item()->dummy_as_man_days_unit('hour') } 'known unit hour'; +lives_ok { new_item()->dummy_as_man_days_unit('man_day') } 'known unit man_day'; + +done_testing();