1 package SL::BackgroundJob::ConvertTimeRecordings;
5 use parent qw(SL::BackgroundJob::Base);
7 use SL::DB::DeliveryOrder;
8 use SL::DB::TimeRecording;
10 use SL::Locale::String qw(t8);
16 $_[0]->create_standard_job('7 3 1 * *'); # every first day of month at 03:07
21 # If job does not throw an error,
22 # success in background_job_histories is 'success'.
23 # It is 'failure' otherwise.
25 # Return value goes to result in background_job_histories.
28 my ($self, $db_obj) = @_;
31 $data = $db_obj->data_as_hash if $db_obj;
33 $self->{$_} = [] for qw(job_errors);
34 # from/to date from data. Defaults to begining and end of last month.
37 # handle errors with a catch handler
39 $from_date = DateTime->from_kivitendo($data->{from_date}) if $data->{from_date};
40 $to_date = DateTime->from_kivitendo($data->{to_date}) if $data->{to_date};
42 die "Cannot convert date from string $data->{from_date} $data->{to_date}\n Details :\n $_"; # not $@
44 $from_date ||= DateTime->new( day => 1, month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1);
45 $to_date ||= DateTime->last_day_of_month(month => DateTime->today_local->month, year => DateTime->today_local->year)->subtract(months => 1);
47 $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)
50 %customer_where = ('customer.customernumber' => $data->{customernumbers}) if 'ARRAY' eq ref $data->{customernumbers};
52 my $time_recordings = SL::DB::Manager::TimeRecording->get_all(where => [end_time => { ge_lt => [ $from_date, $to_date ]},
53 or => [booked => 0, booked => undef],
55 with_objects => ['customer']);
56 my %time_recordings_by_customer_id;
57 push @{ $time_recordings_by_customer_id{$_->customer_id} }, $_ for @$time_recordings;
60 foreach my $customer_id (keys %time_recordings_by_customer_id) {
63 $do = SL::DB::DeliveryOrder->new_from_time_recordings($time_recordings_by_customer_id{$customer_id});
66 $::lxdebug->message(LXDebug->WARN(),
67 "ConvertTimeRecordings: creating delivery order failed ($@) for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_customer_id{$customer_id}});
68 push @{ $self->{job_errors} }, "ConvertTimeRecordings: creating delivery order failed ($@) for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_customer_id{$customer_id}};
73 if (!SL::DB->client->with_transaction(sub {
75 $_->update_attributes(booked => 1) for @{$time_recordings_by_customer_id{$customer_id}};
78 $::lxdebug->message(LXDebug->WARN(),
79 "ConvertTimeRecordings: saving delivery order failed for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_customer_id{$customer_id}});
80 push @{ $self->{job_errors} }, "ConvertTimeRecordings: saving delivery order failed for time recording ids " . join ', ', map { $_->id } @{$time_recordings_by_customer_id{$customer_id}};
82 push @donumbers, $do->donumber;
87 my $msg = t8('Number of delivery orders created:');
89 $msg .= scalar @donumbers;
91 $msg .= join ', ', @donumbers;
93 # die if errors exists
94 if (@{ $self->{job_errors} }) {
95 $msg .= ' ' . t8('The following errors occurred:');
96 $msg .= join "\n", @{ $self->{job_errors} };
105 # from_date: 01.12.2020
106 # to_date: 15.12.2020
107 # customernumbers: [1,2,3]
116 SL::BackgroundJob::ConvertTimeRecordings - Convert time recording
117 entries into delivery orders
121 Get all time recording entries for the given period and customer numbers
122 and create delivery ordes out of that (using
123 C<SL::DB::DeliveryOrder-E<gt>new_from_time_recordings>).
127 Some data can be provided to configure this backgroung job.
128 If there is user data and it cannot be validated the background job
129 returns a error messages.
135 The date from which on time recordings should be collected. It defaults
136 to the first day of the previous month.
138 Example (format depends on your settings):
140 from_date: 01.12.2020
144 The date till which time recordings should be collected. It defaults
145 to the last day of the previous month.
147 Example (format depends on your settings):
151 =item C<customernumbers>
153 An array with the customer numbers for which time recordings should
154 be collected. If not given, time recordings for customers are
155 collected. This is the default.
157 Example (format depends on your settings):
159 customernumbers: [c1,22332,334343]
163 The part id of a time based service which should be used to
164 book the times. If not set the clients config defaults is used.
168 If set the 0 no rounding of the times will be done otherwise
169 the times will be rounded up to th full quarters of an hour,
170 ie. 0.25h 0.5h 0.75h 1.25h ...
171 Defaults to rounding true (1).
173 =item C<link_project>
175 If set the job tries to find a previous Order with the current
176 customer and project number and tries to do as much automatic
177 workflow processing as the UI.
178 Defaults to off. If set to true (1) the job will fail if there
179 is no Sales Orders which qualifies as a predecessor.
180 Conditions for a predeccesor:
182 * Global project_id must match time_recording.project_id OR data.project_id
183 * Customer name must match time_recording.customer_id OR data.customernumbers
184 * The sales order must have at least one or more time related services
185 * The Project needs to be valid and active
187 The job doesn't care if the Sales Order is already delivered or closed.
188 If the sales order is overdelivered some organisational stuff needs to be done.
189 The sales order may also already be closed, ie the amount is fully billed, but
190 the services are not yet fully delivered (simple case: 'Payment in advance').
192 Hint: take a look or extend the job CloseProjectsBelongingToClosedSalesOrder for
193 further automatisation of your organisational needs.
198 Use this project_id instead of the project_id in the time recordings.
204 Bernd Bleßmann E<lt>bernd@kivitendo-premium.deE<gt>