Zeiterfassung: Konvertierung: Artikel/Projekt: override- und default-Parameter
authorBernd Bleßmann <bernd@kivitendo-premium.de>
Wed, 5 May 2021 16:03:31 +0000 (18:03 +0200)
committerBernd Bleßmann <bernd@kivitendo-premium.de>
Wed, 5 May 2021 16:39:18 +0000 (18:39 +0200)
SL/BackgroundJob/ConvertTimeRecordings.pm
SL/DB/DeliveryOrder.pm
t/background_job/convert_time_recordings.t

index 75c09ad..ff8c79b 100644 (file)
@@ -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<part_id>
+=item C<override_part_id>
+
+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<default_part_id>
 
 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<project_id>
+=item C<override_project_id>
 
-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<link_order> is true.
+
+=item C<default_project_id>
+
+Use this project id if no project id is set in the time recording
+entry. This is only used if C<link_order> 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
 
index 8a0ada7..395a449 100644 (file)
@@ -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<SL::DB::DeliveryOrder> 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<default_part_id>
+
+An optional part id which is used as default value if no part is set
+in the time recording entry.
+
+=item C<default_partnumber>
+
+Like C<default_part_id> but given as partnumber, not as id.
+
+=item C<override_part_id>
+
+An optional part id which is used instead of a value set in the time
+recording entry.
+
+=item C<override_partnumber>
+
+Like C<overrride_part_id> but given as partnumber, not as id.
+
 =item C<related_order>
 
 An optional C<SL::DB::Order> object. If it exists then it is used to
index 32e2828..454af11 100644 (file)
@@ -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');
 
 #####