From 662df9d7fdc9c953d324a975a5bf554e6534c42e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Bernd=20Ble=C3=9Fmann?= Date: Wed, 5 May 2021 18:03:31 +0200 Subject: [PATCH] Zeiterfassung: Konvertierung: Artikel/Projekt: override- und default-Parameter --- SL/BackgroundJob/ConvertTimeRecordings.pm | 85 ++++++----- SL/DB/DeliveryOrder.pm | 36 ++++- t/background_job/convert_time_recordings.t | 158 ++++++++++++++++++--- 3 files changed, 218 insertions(+), 61 deletions(-) diff --git a/SL/BackgroundJob/ConvertTimeRecordings.pm b/SL/BackgroundJob/ConvertTimeRecordings.pm index 75c09ad34..ff8c79b61 100644 --- a/SL/BackgroundJob/ConvertTimeRecordings.pm +++ b/SL/BackgroundJob/ConvertTimeRecordings.pm @@ -85,13 +85,15 @@ sub initialize_params { # valid parameters with default values my %valid_params = ( - from_date => DateTime->new( day => 1, month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, - to_date => DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, - customernumbers => [], - part_id => undef, - project_id => undef, - rounding => 1, - link_order => 0, + from_date => DateTime->new( day => 1, month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, + to_date => DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1)->to_kivitendo, + customernumbers => [], + override_part_id => undef, + default_part_id => undef, + override_project_id => undef, + default_project_id => undef, + rounding => 1, + link_order => 0, ); @@ -136,16 +138,24 @@ sub initialize_params { # check part - if ($self->params->{part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{part_id}, - or => [obsolete => undef, obsolete => 0])) { - die 'No valid part found by given part id'; + if ($self->params->{override_part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{override_part_id}, + or => [obsolete => undef, obsolete => 0])) { + die 'No valid part found by given override part id'; + } + if ($self->params->{default_part_id} && !SL::DB::Manager::Part->find_by(id => $self->params->{default_part_id}, + or => [obsolete => undef, obsolete => 0])) { + die 'No valid part found by given default part id'; } # check project - if ($self->params->{project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{project_id}, - active => 1, valid => 1)) { - die 'No valid project found by given project id'; + if ($self->params->{override_project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{override_project_id}, + active => 1, valid => 1)) { + die 'No valid project found by given override project id'; + } + if ($self->params->{default_project_id} && !SL::DB::Manager::Project->find_by(id => $self->params->{default_project_id}, + active => 1, valid => 1)) { + die 'No valid project found by given default project id'; } return $self->params; @@ -158,8 +168,9 @@ sub convert_without_linking { push @{ $time_recordings_by_customer_id{$_->customer_id} }, $_ for @$time_recordings; my %convert_params = ( - rounding => $self->params->{rounding}, - default_part_id => $self->params->{part_id}, + rounding => $self->params->{rounding}, + override_part_id => $self->params->{override_part_id}, + default_part_id => $self->params->{default_part_id}, ); my @donumbers; @@ -193,7 +204,8 @@ sub convert_with_linking { my %convert_params = ( rounding => $self->params->{rounding}, - default_part_id => $self->params->{part_id}, + override_part_id => $self->params->{override_part_id}, + default_part_id => $self->params->{default_part_id}, ); my @donumbers; @@ -257,10 +269,9 @@ sub get_order_for_time_recording { if (!$tr->order_id) { # check project my $project_id; - #$project_id = $self->override_project_id; - $project_id = $self->params->{project_id}; + $project_id = $self->params->{override_project_id}; $project_id ||= $tr->project_id; - #$project_id ||= $self->default_project_id; + $project_id ||= $self->params->{default_project_id}; if (!$project_id) { $self->log_error('searching related order failed for time recording id ' . $tr->id . ' : no project id'); @@ -300,10 +311,9 @@ sub get_order_for_time_recording { # check part my $part_id; - #$part_id = $self->override_part_id; + $part_id = $self->params->{override_part_id}; $part_id ||= $tr->part_id; - #$part_id ||= $self->default_part_id; - $part_id ||= $self->params->{part_id}; + $part_id ||= $self->params->{default_part_id}; if (!$part_id) { $self->log_error('searching related order failed for time recording id ' . $tr->id . ' : no part id'); @@ -339,7 +349,7 @@ sub get_order_for_time_recording { return; } - if ($tr->project_id && $tr->project_id != ($matching_order->globalproject_id || 0)) { + if ($tr->project_id && !$self->params->{override_project_id} && $tr->project_id != ($matching_order->globalproject_id || 0)) { $self->log_error('searching related order failed for time recording id ' . $tr->id . ' : project of order does not match project of time recording'); return; } @@ -415,7 +425,13 @@ collected. customernumbers: [c1,22332,334343] -=item C +=item C + +The part id of a time based service which should be used to +book the times instead of the parts which are set in the time +recordings. + +=item C The part id of a time based service which should be used to book the times if no part is set in the time recording entry. @@ -452,9 +468,15 @@ the services are not yet fully delivered (simple case: 'Payment in advance'). Hint: take a look or extend the job CloseProjectsBelongingToClosedSalesOrder for further automatisation of your organisational needs. -=item C +=item C -Use this project_id instead of the project_id in the time recordings. +Use this project id instead of the project id in the time recordings to find +a related order. This is only used if C is true. + +=item C + +Use this project id if no project id is set in the time recording +entry. This is only used if C is true. =back @@ -468,17 +490,6 @@ Add parameters to give part and project not with their ids, but with their numbers. E.g. (default_/override_)part_number, (default_/override_)project_number. -=item * part and project parameters override and default - -In the moment, the part id given as parameter is used as the default value. -This means, it will be used if there is no part in the time recvording entry. - -The project id given is used as override parameter. It overrides the project -given in the time recording entry. - -To solve this, there should be parameters named override_part_id, -default_part_id, override_project_id and default_project_id. - =back diff --git a/SL/DB/DeliveryOrder.pm b/SL/DB/DeliveryOrder.pm index 8a0ada7e1..395a44944 100644 --- a/SL/DB/DeliveryOrder.pm +++ b/SL/DB/DeliveryOrder.pm @@ -194,9 +194,12 @@ sub new_from_time_recordings { # - merge same descriptions # - my $default_part_id = $params{default_part_id} ? $params{default_part_id} - : $params{default_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id - : undef; + my $default_part_id = $params{default_part_id} ? $params{default_part_id} + : $params{default_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{default_partnumber})->id + : undef; + my $override_part_id = $params{override_part_id} ? $params{override_part_id} + : $params{override_partnumber} ? SL::DB::Manager::Part->find_by(partnumber => $params{override_partnumber})->id + : undef; # check parts and collect entries my %part_by_part_id; @@ -204,9 +207,9 @@ sub new_from_time_recordings { foreach my $source (@$sources) { next if !$source->duration; - my $part_id = $source->part_id ? $source->part_id - : $default_part_id ? $default_part_id - : undef; + my $part_id = $override_part_id; + $part_id ||= $source->part_id; + $part_id ||= $default_part_id; die 'article not found for entry "' . $source->displayable_times . '"' if !$part_id; @@ -434,7 +437,8 @@ Creates a new C instance from the time recordings given as C<$sources>. All time recording entries must belong to the same customer. Time recordings are sorted by article and date. For each article a new delivery order item is created. If no article is associated with an -entry, a default article will be used. +entry, a default article will be used. The article given in the time +recording entry can be overriden. Entries of the same date (for each article) are summed together and form a list entry in the long description of the item. @@ -451,6 +455,24 @@ C<%params> can include the following options: An optional hash reference. If it exists then it is used to set attributes of the newly created delivery order object. +=item C + +An optional part id which is used as default value if no part is set +in the time recording entry. + +=item C + +Like C but given as partnumber, not as id. + +=item C + +An optional part id which is used instead of a value set in the time +recording entry. + +=item C + +Like C but given as partnumber, not as id. + =item C An optional C object. If it exists then it is used to diff --git a/t/background_job/convert_time_recordings.t b/t/background_job/convert_time_recordings.t index 32e282863..454af117a 100644 --- a/t/background_job/convert_time_recordings.t +++ b/t/background_job/convert_time_recordings.t @@ -1,4 +1,4 @@ -use Test::More tests => 40; +use Test::More tests => 52; use strict; @@ -37,7 +37,7 @@ $::locale = Locale->new('en'); clear_up(); ######################################## -# two time recordings, one order linked with project_id +# two time recordings, one order linked with project_id in time recording entry ######################################## my $part = new_service(partnumber => 'Serv1', unit => 'Std')->save; my $project = create_project(projectnumber => 'p1', description => 'Project 1'); @@ -70,7 +70,6 @@ push @time_recordings, new_time_recording( my %data = ( link_order => 1, - project_id => $project->id, from_date => '01.01.2021', to_date => '30.04.2021', ); @@ -102,7 +101,7 @@ clear_up(); ######################################## -# two time recordings, one order linked with project_id +# two time recordings, one order linked with project_id in time recording entry # unit in order is 'min', but part is 'Std' ######################################## $part = new_service(partnumber => 'Serv1', unit => 'Std')->save; @@ -133,10 +132,8 @@ push @time_recordings, new_time_recording( part => $part, )->save; -# two time recordings, one order linked with project_id %data = ( link_order => 1, - project_id => $project->id, from_date => '01.04.2021', to_date => '30.04.2021', ); @@ -161,7 +158,7 @@ clear_up(); ######################################## -# two time recordings, one order linked with project_id +# two time recordings, one order linked with project_id in time recording entry # unit in order is 'Std', but part is 'min' ######################################## $part = new_service(partnumber => 'Serv1', unit => 'min')->save; @@ -192,10 +189,8 @@ push @time_recordings, new_time_recording( part => $part, )->save; -# two time recordings, one order linked with project_id %data = ( link_order => 1, - project_id => $project->id, from_date => '01.04.2021', to_date => '30.04.2021', ); @@ -313,6 +308,135 @@ is($sales_order->items->[0]->ship*1, 1, 'linked by order_id: ship in related ord clear_up(); +######################################## +# override project and part +######################################## +$part = new_service(partnumber => 'Serv1', unit => 'Std')->save; +my $part2 = new_service(partnumber => 'Serv2', unit => 'min')->save; +$project = create_project(projectnumber => 'p1', description => 'Project 1'); +my $project2 = create_project(projectnumber => 'p2', description => 'Project 2'); +$customer = new_customer()->save; + +$sales_order = create_sales_order( + save => 1, + customer => $customer, + globalproject => $project, + taxincluded => 0, + orderitems => [ create_order_item(part => $part, qty => 180, unit => 'min', sellprice => 70), ] +); +my $sales_order2 = create_sales_order( + save => 1, + customer => $customer, + globalproject => $project, + taxincluded => 0, + orderitems => [ create_order_item(part => $part2, qty => 180, unit => 'min', sellprice => 70), ] +); + +new_time_recording( + start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 10, minute => 10), + end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 10), + customer => $customer, + project => $project, + part => $part, +)->save; +new_time_recording( + start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 10), + end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 12, minute => 10), + customer => $customer, + project => $project2, + part => $part2, +)->save; +new_time_recording( + start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 12, minute => 10), + end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 13, minute => 10), + customer => $customer, +)->save; + +%data = ( + link_order => 1, + from_date => '01.04.2021', + to_date => '30.04.2021', + override_part_id => $part->id, + override_project_id => $project->id, +); +$db_obj = SL::DB::BackgroundJob->new(); +$db_obj->set_data(%data); +$job = SL::BackgroundJob::ConvertTimeRecordings->new; +$ret = $job->run($db_obj); + +$linked_dos = $sales_order->linked_records(to => 'DeliveryOrder'); +is($linked_dos->[0]->globalproject_id, $project->id, 'overriden part and project: project in delivery order'); + +$linked_items = $sales_order->items->[0]->linked_records(to => 'DeliveryOrderItem'); +is($linked_items->[0]->qty*1, 3, 'overriden part and project: qty in delivery order'); +is($linked_items->[0]->base_qty*1, 3, 'overriden part and project: base_qty in delivery order'); +is($linked_items->[0]->parts_id, $part->id, 'overriden part and project: part id'); + +my $linked_dos2 = $sales_order2->linked_records(to => 'DeliveryOrder'); +is(scalar @$linked_dos2, 0, 'overriden part and project: no delivery order for unused order'); + +# reload order and orderitems to get changes to deliverd and ship +Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems'); +$sales_order->load; +Rose::DB::Object::Helpers::forget_related($sales_order2, 'orderitems'); +$sales_order2->load; + +is($sales_order ->items->[0]->ship||0, 180, 'overriden part and project: ship in related order'); +is($sales_order2->items->[0]->ship||0, 0, 'overriden part and project: ship in not related order'); + +clear_up(); + + +######################################## +# default project and part +######################################## +$part = new_service(partnumber => 'Serv1', unit => 'Std')->save; +$project = create_project(projectnumber => 'p1', description => 'Project 1'); +$customer = new_customer()->save; + +$sales_order = create_sales_order( + save => 1, + customer => $customer, + globalproject => $project, + taxincluded => 0, + orderitems => [ create_order_item(part => $part, qty => 180, unit => 'min', sellprice => 70), ] +); + +new_time_recording( + start_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 10, minute => 10), + end_time => DateTime->new(year => 2021, month => 4, day => 19, hour => 11, minute => 40), + customer => $customer, +)->save; + +%data = ( + link_order => 1, + from_date => '01.04.2021', + to_date => '30.04.2021', + default_part_id => $part->id, + default_project_id => $project->id, +); +$db_obj = SL::DB::BackgroundJob->new(); +$db_obj->set_data(%data); +$job = SL::BackgroundJob::ConvertTimeRecordings->new; +$ret = $job->run($db_obj); + +$linked_dos = $sales_order->linked_records(to => 'DeliveryOrder'); +is($linked_dos->[0]->globalproject_id, $project->id, 'default and project: project in delivery order'); + +$linked_items = $sales_order->items->[0]->linked_records(to => 'DeliveryOrderItem'); +is($linked_items->[0]->qty*1, 1.5, 'default part and project: qty in delivery order'); +is($linked_items->[0]->base_qty*1, 1.5, 'default part and project: base_qty in delivery order'); +is($linked_items->[0]->parts_id, $part->id, 'default part and project: part id'); + +# reload order and orderitems to get changes to deliverd and ship +Rose::DB::Object::Helpers::forget_related($sales_order, 'orderitems'); +$sales_order->load; + +is($sales_order->items->[0]->ship*1, 90, 'default part and project: ship in related order'); + +clear_up(); + + ######################################## # check rounding ######################################## @@ -453,7 +577,7 @@ ok($err_msg =~ '^Customer numbers must be given in an array', 'wrong customer nu ##### %data = ( - part_id => '123', + override_part_id => '123', ); $db_obj = SL::DB::BackgroundJob->new(); @@ -462,13 +586,13 @@ $job = SL::BackgroundJob::ConvertTimeRecordings->new; $err_msg = ''; eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; -ok($err_msg =~ '^No valid part found by given part id', 'invalid part id detected'); +ok($err_msg =~ '^No valid part found by given override part id', 'invalid part id detected'); ##### $part = new_service(partnumber => 'Serv1', unit => 'Std', obsolete => 1)->save; %data = ( - part_id => $part->id, + override_part_id => $part->id, ); $db_obj = SL::DB::BackgroundJob->new(); @@ -477,12 +601,12 @@ $job = SL::BackgroundJob::ConvertTimeRecordings->new; $err_msg = ''; eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; -ok($err_msg =~ '^No valid part found by given part id', 'obsolete part detected'); +ok($err_msg =~ '^No valid part found by given override part id', 'obsolete part detected'); ##### %data = ( - project_id => 123, + override_project_id => 123, ); $db_obj = SL::DB::BackgroundJob->new(); @@ -491,13 +615,13 @@ $job = SL::BackgroundJob::ConvertTimeRecordings->new; $err_msg = ''; eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; -ok($err_msg =~ '^No valid project found by given project id', 'invalid project id detected'); +ok($err_msg =~ '^No valid project found by given override project id', 'invalid project id detected'); ##### $project = create_project(projectnumber => 'p1', description => 'Project 1', valid => 0)->save; %data = ( - project_id => $project->id, + override_project_id => $project->id, ); $db_obj = SL::DB::BackgroundJob->new(); @@ -506,7 +630,7 @@ $job = SL::BackgroundJob::ConvertTimeRecordings->new; $err_msg = ''; eval { $ret = $job->run($db_obj); 1; } or do {$err_msg = $@}; -ok($err_msg =~ '^No valid project found by given project id', 'invalid project detected'); +ok($err_msg =~ '^No valid project found by given override project id', 'invalid project detected'); ##### -- 2.20.1