Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 08704bc4

Von Bernd Bleßmann vor mehr als 3 Jahren hinzugefügt

  • ID 08704bc49080b1f74c90fc49adf2958d491a059e
  • Vorgänger c968d1f7
  • Nachfolger 9c2d09b8

Zeiterfassung: Parameter f. Konvertierung mit link_project/related order

Unterschiede anzeigen:

SL/BackgroundJob/ConvertTimeRecordings.pm
5 5
use parent qw(SL::BackgroundJob::Base);
6 6

  
7 7
use SL::DB::DeliveryOrder;
8
use SL::DB::Part;
9
use SL::DB::Project;
8 10
use SL::DB::TimeRecording;
9

  
10 11
use SL::Locale::String qw(t8);
11 12

  
12 13
use Carp;
13 14
use DateTime;
15
use List::Util qw(any);
14 16
use Try::Tiny;
15 17

  
16 18
sub create_job {
17 19
  $_[0]->create_standard_job('7 3 1 * *'); # every first day of month at 03:07
18 20
}
19 21
use Rose::Object::MakeMethods::Generic (
22
 'scalar'                => [ qw(data) ],
20 23
 'scalar --get_set_init' => [ qw(rounding link_project) ],
21 24
);
22 25

  
......
41 44
sub run {
42 45
  my ($self, $db_obj) = @_;
43 46

  
44
  my $data;
45
  $data = $db_obj->data_as_hash if $db_obj;
47
  $self->data($db_obj->data_as_hash) if $db_obj;
46 48

  
47 49
  $self->{$_} = [] for qw(job_errors);
48 50

  
49 51
  # check user input param names
50
  foreach my $param (keys %{ $data }) {
52
  foreach my $param (keys %{ $self->data }) {
51 53
    die "Not a valid parameter: $param" unless exists $valid_params{$param};
52 54
  }
53 55

  
......
62 64
  my $to_date;
63 65
  # handle errors with a catch handler
64 66
  try {
65
    $from_date   = DateTime->from_kivitendo($data->{from_date}) if $data->{from_date};
66
    $to_date     = DateTime->from_kivitendo($data->{to_date})   if $data->{to_date};
67
    $from_date   = DateTime->from_kivitendo($self->data->{from_date}) if $self->data->{from_date};
68
    $to_date     = DateTime->from_kivitendo($self->data->{to_date})   if $self->data->{to_date};
67 69
  } catch {
68
    die "Cannot convert date from string $data->{from_date} $data->{to_date}\n Details :\n $_"; # not $@
70
    die "Cannot convert date from string $self->data->{from_date} $self->data->{to_date}\n Details :\n $_"; # not $@
69 71
  };
70 72
  $from_date ||= DateTime->new( day => 1,    month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1);
71 73
  $to_date   ||= DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1);
......
73 75
  $to_date->add(days => 1); # to get all from the to_date, because of the time part (15.12.2020 23.59 > 15.12.2020)
74 76

  
75 77
  my %customer_where;
76
  %customer_where = ('customer.customernumber' => $data->{customernumbers}) if 'ARRAY' eq ref $data->{customernumbers};
78
  %customer_where = ('customer.customernumber' => $self->data->{customernumbers}) if 'ARRAY' eq ref $self->data->{customernumbers};
77 79

  
78 80
  my $time_recordings = SL::DB::Manager::TimeRecording->get_all(where        => [end_time => { ge_lt => [ $from_date, $to_date ]},
79 81
                                                                                 or => [booked => 0, booked => undef],
......
83 85
  # no time recordings at all ? -> better exit here before iterating a empty hash
84 86
  # return undef or message unless ref $time_recordings->[0] eq SL::DB::Manager::TimeRecording;
85 87

  
88
  my @donumbers;
89

  
90
  if ($self->data->{link_project}) {
91
    my %time_recordings_by_order_id;
92
    my %orders_by_order_id;
93
    foreach my $tr (@$time_recordings) {
94
      my $order = $self->get_order_for_time_recording($tr);
95
      next if !$order;
96
      push @{ $time_recordings_by_order_id{$order->id} }, $tr;
97
      $orders_by_order_id{$order->id} ||= $order;
98
    }
99
    @donumbers = $self->convert_with_linking(\%time_recordings_by_order_id, \%orders_by_order_id);
100

  
101
  } else {
102
    @donumbers = $self->convert_without_linking($time_recordings);
103
  }
104

  
105
  my $msg  = t8('Number of delivery orders created:');
106
  $msg    .= ' ';
107
  $msg    .= scalar @donumbers;
108
  $msg    .= ' (';
109
  $msg    .= join ', ', @donumbers;
110
  $msg    .= ').';
111
  # die if errors exists
112
  if (@{ $self->{job_errors} }) {
113
    $msg  .= ' ' . t8('The following errors occurred:');
114
    $msg  .= ' ';
115
    $msg  .= join "\n", @{ $self->{job_errors} };
116
    die $msg . "\n";
117
  }
118
  return $msg;
119
}
120

  
121
# inits
122

  
123
sub init_rounding {
124
  1
125
}
126

  
127
sub init_link_project {
128
  0
129
}
130

  
131
# helper
132
sub convert_without_linking {
133
  my ($self, $time_recordings) = @_;
134

  
86 135
  my %time_recordings_by_customer_id;
87 136
  push @{ $time_recordings_by_customer_id{$_->customer_id} }, $_ for @$time_recordings;
88 137

  
89
  my %convert_params = map { $_ => $data->{$_} } qw(rounding link_project part_id project_id);
138
  my %convert_params = map { $_ => $self->data->{$_} } qw(rounding link_project project_id);
139
  $convert_params{default_part_id} = $self->data->{part_id};
90 140

  
91 141
  my @donumbers;
92 142
  foreach my $customer_id (keys %time_recordings_by_customer_id) {
......
116 166
    }
117 167
  }
118 168

  
119
  my $msg  = t8('Number of delivery orders created:');
120
  $msg    .= ' ';
121
  $msg    .= scalar @donumbers;
122
  $msg    .= ' (';
123
  $msg    .= join ', ', @donumbers;
124
  $msg    .= ').';
125
  # die if errors exists
126
  if (@{ $self->{job_errors} }) {
127
    $msg  .= ' ' . t8('The following errors occurred:');
128
    $msg  .= ' ';
129
    $msg  .= join "\n", @{ $self->{job_errors} };
130
    die $msg . "\n";
131
  }
132
  return $msg;
169
  return @donumbers;
133 170
}
134 171

  
135
# inits
172
sub convert_with_linking {
173
  my ($self, $time_recordings_by_order_id, $orders_by_order_id) = @_;
136 174

  
137
sub init_rounding {
138
  1
175
  my %convert_params = map { $_ => $self->data->{$_} } qw(rounding link_project project_id);
176
  $convert_params{default_part_id} = $self->data->{part_id};
177

  
178
  my @donumbers;
179
  foreach my $related_order_id (keys %$time_recordings_by_order_id) {
180
    my $related_order = $orders_by_order_id->{$related_order_id};
181
    my $do;
182
    if (!eval {
183
      $do = SL::DB::DeliveryOrder->new_from_time_recordings($time_recordings_by_order_id->{$related_order_id}, related_order => $related_order, %convert_params);
184
      1;
185
    }) {
186
      $::lxdebug->message(LXDebug->WARN(),
187
                          "ConvertTimeRecordings: creating delivery order failed ($@) for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_order_id->{$related_order_id}});
188
      push @{ $self->{job_errors} }, "ConvertTimeRecordings: creating delivery order failed ($@) for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_order_id->{$related_order_id}};
189
    }
190

  
191
    if ($do) {
192
      if (!SL::DB->client->with_transaction(sub {
193
        $do->save;
194
        $_->update_attributes(booked => 1) for @{$time_recordings_by_order_id->{$related_order_id}};
195

  
196
        # Todo: reduce qty on related order
197
        1;
198
      })) {
199
        $::lxdebug->message(LXDebug->WARN(),
200
                            "ConvertTimeRecordings: saving delivery order failed for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_order_id->{$related_order_id}});
201
        push @{ $self->{job_errors} }, "ConvertTimeRecordings: saving delivery order failed for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_order_id->{$related_order_id}};
202
      } else {
203
        push @donumbers, $do->donumber;
204
      }
205
    }
206
  }
207

  
208
  return @donumbers;
139 209
}
140 210

  
141
sub init_link_project {
142
  0
211
sub get_order_for_time_recording {
212
  my ($self, $tr) = @_;
213

  
214
  # check project
215
  my $project_id;
216
  #$project_id   = $self->overide_project_id;
217
  $project_id   = $self->data->{project_id};
218
  $project_id ||= $tr->project_id;
219
  #$project_id ||= $self->default_project_id;
220

  
221
  if (!$project_id) {
222
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : no project id';
223
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
224
    push @{ $self->{job_errors} }, $err_msg;
225
    return;
226
  }
227

  
228
  my $project = SL::DB::Project->load_cached($project_id);
229

  
230
  if (!$project) {
231
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : project not found';
232
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
233
    push @{ $self->{job_errors} }, $err_msg;
234
    return;
235
  }
236
  if (!$project->active || !$project->valid) {
237
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : project not active or not valid';
238
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
239
    push @{ $self->{job_errors} }, $err_msg;
240
    return;
241
  }
242
  if ($project->customer_id && $project->customer_id != $tr->customer_id) {
243
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : project customer does not match customer of time recording';
244
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
245
    push @{ $self->{job_errors} }, $err_msg;
246
    return;
247
  }
248

  
249
  # check part
250
  my $part_id;
251
  #$part_id   = $self->overide_part_id;
252
  $part_id ||= $tr->part_id;
253
  #$part_id ||= $self->default_part_id;
254
  $part_id ||= $self->data->{part_id};
255

  
256
  if (!$part_id) {
257
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : no part id';
258
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
259
    push @{ $self->{job_errors} }, $err_msg;
260
    return;
261
  }
262
  my $part = SL::DB::Part->load_cached($part_id);
263
  if (!$part->unit_obj->is_time_based) {
264
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : part unit is not time based';
265
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
266
    push @{ $self->{job_errors} }, $err_msg;
267
    return;
268
  }
269

  
270
  my $orders = SL::DB::Manager::Order->get_all(where => [customer_id      => $tr->customer_id,
271
                                                         or               => [quotation => undef, quotation => 0],
272
                                                         globalproject_id => $project_id, ]);
273
  my @matching_orders;
274
  foreach my $order (@$orders) {
275
    if (any { $_->parts_id == $part_id } @{ $order->items_sorted }) {
276
      push @matching_orders, $order;
277
    }
278
  }
279

  
280
  if (1 != scalar @matching_orders) {
281
    my $err_msg = 'ConvertTimeRecordings: searching related order failed for time recording id ' . $tr->id . ' : no or more than one orders do match';
282
    $::lxdebug->message(LXDebug->WARN(), $err_msg);
283
    push @{ $self->{job_errors} }, $err_msg;
284
    return;
285
  }
286

  
287
  return $matching_orders[0];
143 288
}
144 289

  
145 290
1;
SL/DB/DeliveryOrder.pm
185 185
  croak("Unsupported object type in sources")                                      if any { ref($_) ne 'SL::DB::TimeRecording' }            @$sources;
186 186
  croak("Cannot create delivery order from source records of different customers") if any { $_->customer_id != $sources->[0]->customer_id } @$sources;
187 187

  
188
  my %args = (
189
    is_sales    => 1,
190
    delivered   => 0,
191
    customer_id => $sources->[0]->customer_id,
192
    taxzone_id  => $sources->[0]->customer->taxzone_id,
193
    currency_id => $sources->[0]->customer->currency_id,
194
    employee_id => SL::DB::Manager::Employee->current->id,
195
    salesman_id => SL::DB::Manager::Employee->current->id,
196
    items       => [],
197
  );
198
  my $delivery_order = $class->new(%args);
199
  $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
200

  
201 188
  # - one item per part (article)
202 189
  # - qty is sum of duration
203 190
  # - description goes to item longdescription
......
239 226
    $entries->{$part_id}->{$date}->{date_obj}  = $source->start_time; # for sorting
240 227
  }
241 228

  
229
  my @items;
230

  
242 231
  my $h_unit = SL::DB::Manager::Unit->find_h_unit;
243 232

  
244 233
  my @keys = sort { $part_by_part_id{$a}->partnumber cmp $part_by_part_id{$b}->partnumber } keys %$entries;
......
267 256
      longdescription => $longdescription,
268 257
    );
269 258

  
270
    $delivery_order->add_items($item);
259
    push @items, $item;
260
  }
261

  
262
  my $delivery_order;
263

  
264
  if ($params{related_order}) {
265
    $delivery_order = SL::DB::DeliveryOrder->new_from($params{related_order}, items => \@items, %params);
266

  
267
  } else {
268
    my %args = (
269
      is_sales    => 1,
270
      delivered   => 0,
271
      customer_id => $sources->[0]->customer_id,
272
      taxzone_id  => $sources->[0]->customer->taxzone_id,
273
      currency_id => $sources->[0]->customer->currency_id,
274
      employee_id => SL::DB::Manager::Employee->current->id,
275
      salesman_id => SL::DB::Manager::Employee->current->id,
276
      items       => \@items,
277
    );
278
    $delivery_order = $class->new(%args);
279
    $delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes};
271 280
  }
272 281

  
273 282
  return $delivery_order;

Auch abrufbar als: Unified diff