Revision 2632ced6
Von Bernd Bleßmann vor mehr als 3 Jahren hinzugefügt
SL/DB/DeliveryOrder.pm | ||
---|---|---|
14 | 14 |
use SL::DB::Helper::LinkedRecords; |
15 | 15 |
use SL::DB::Helper::TransNumberGenerator; |
16 | 16 |
|
17 |
use List::Util qw(first); |
|
17 |
use SL::DB::Part; |
|
18 |
use SL::DB::Unit; |
|
19 |
|
|
20 |
use SL::Helper::Number qw(_format_total _round_total); |
|
21 |
|
|
22 |
use List::Util qw(first notall); |
|
23 |
use List::MoreUtils qw(any); |
|
18 | 24 |
|
19 | 25 |
__PACKAGE__->meta->add_relationship(orderitems => { type => 'one to many', |
20 | 26 |
class => 'SL::DB::DeliveryOrderItem', |
... | ... | |
172 | 178 |
return $delivery_order; |
173 | 179 |
} |
174 | 180 |
|
181 |
sub new_from_time_recordings { |
|
182 |
my ($class, $sources, %params) = @_; |
|
183 |
|
|
184 |
croak("Unsupported object type in sources") if any { ref($_) ne 'SL::DB::TimeRecording' } @$sources; |
|
185 |
croak("Cannot create delivery order from source records of different customers") if any { $_->customer_id != $sources->[0]->customer_id } @$sources; |
|
186 |
|
|
187 |
my %args = ( |
|
188 |
is_sales => 1, |
|
189 |
delivered => 1, |
|
190 |
customer_id => $sources->[0]->customer_id, |
|
191 |
taxzone_id => $sources->[0]->customer->taxzone_id, |
|
192 |
currency_id => $sources->[0]->customer->currency_id, |
|
193 |
employee_id => SL::DB::Manager::Employee->current->id, |
|
194 |
salesman_id => SL::DB::Manager::Employee->current->id, |
|
195 |
items => [], |
|
196 |
); |
|
197 |
my $delivery_order = $class->new(%args); |
|
198 |
$delivery_order->assign_attributes(%{ $params{attributes} }) if $params{attributes}; |
|
199 |
|
|
200 |
# - one item per part (article) |
|
201 |
# - qty is sum of duration |
|
202 |
# - description goes to item longdescription |
|
203 |
# - ordered and summed by date |
|
204 |
# - each description goes to an ordered list |
|
205 |
# - (as time recording descriptions are formatted text by now, use stripped text) |
|
206 |
# - merge same descriptions (todo) |
|
207 |
# |
|
208 |
|
|
209 |
### config |
|
210 |
my $default_partnummer = 6; |
|
211 |
my $default_part_id = SL::DB::Manager::Part->find_by(partnumber => $default_partnummer)->id; |
|
212 |
|
|
213 |
# check parts and collect entries |
|
214 |
my %part_by_part_id; |
|
215 |
my $entries; |
|
216 |
foreach my $source (@$sources) { |
|
217 |
my $part_id = $source->part_id ? $source->part_id |
|
218 |
: $default_part_id ? $default_part_id |
|
219 |
: undef; |
|
220 |
|
|
221 |
die 'article not found for entry "' . $source->displayable_times . '"' if !$part_id; |
|
222 |
|
|
223 |
if (!$part_by_part_id{$part_id}) { |
|
224 |
$part_by_part_id{$part_id} = SL::DB::Part->new(id => $part_id)->load; |
|
225 |
die 'article unit must be time based for entry "' . $source->displayable_times . '"' if !$part_by_part_id{$part_id}->unit_obj->is_time_based; |
|
226 |
} |
|
227 |
|
|
228 |
my $date = $source->start_time->to_kivitendo; |
|
229 |
$entries->{$part_id}->{$date}->{duration} += _round_total($source->duration_in_hours); |
|
230 |
$entries->{$part_id}->{$date}->{content} .= '<li>' . $source->description_as_stripped_html . '</li>'; |
|
231 |
$entries->{$part_id}->{$date}->{date_obj} = $source->start_time; # for sorting |
|
232 |
} |
|
233 |
|
|
234 |
my $h_unit = SL::DB::Manager::Unit->find_h_unit; |
|
235 |
|
|
236 |
my @keys = sort { $part_by_part_id{$a}->partnumber cmp $part_by_part_id{$b}->partnumber } keys %$entries; |
|
237 |
foreach my $key (@keys) { |
|
238 |
my $qty = 0; |
|
239 |
my $longdescription = ''; |
|
240 |
|
|
241 |
my @dates = sort { $entries->{$key}->{$a}->{date_obj} <=> $entries->{$key}->{$b}->{date_obj} } keys %{$entries->{$key}}; |
|
242 |
foreach my $date (@dates) { |
|
243 |
my $entry = $entries->{$key}->{$date}; |
|
244 |
|
|
245 |
$qty += $entry->{duration}; |
|
246 |
$longdescription .= $date . ' <strong>' . _format_total($entry->{duration}) . ' h</strong>'; |
|
247 |
$longdescription .= '<ul>'; |
|
248 |
$longdescription .= $entry->{content}; |
|
249 |
$longdescription .= '</ul>'; |
|
250 |
} |
|
251 |
|
|
252 |
my $item = SL::DB::DeliveryOrderItem->new( |
|
253 |
parts_id => $part_by_part_id{$key}->id, |
|
254 |
description => $part_by_part_id{$key}->description, |
|
255 |
qty => $qty, |
|
256 |
unit_obj => $h_unit, |
|
257 |
sellprice => $part_by_part_id{$key}->sellprice, |
|
258 |
longdescription => $longdescription, |
|
259 |
); |
|
260 |
|
|
261 |
$delivery_order->add_items($item); |
|
262 |
} |
|
263 |
|
|
264 |
return $delivery_order; |
|
265 |
} |
|
266 |
|
|
175 | 267 |
sub customervendor { |
176 | 268 |
$_[0]->is_sales ? $_[0]->customer : $_[0]->vendor; |
177 | 269 |
} |
... | ... | |
301 | 393 |
|
302 | 394 |
=back |
303 | 395 |
|
396 |
=item C<new_from_time_recordings $sources, %params> |
|
397 |
|
|
398 |
Creates a new C<SL::DB::DeliveryOrder> instace from the time recordings |
|
399 |
given as C<$sources>. All time recording entries must belong to the same |
|
400 |
customer. Time recordings are sorted by article and date. For each article |
|
401 |
a new delivery order item is created. If no article is associated with an |
|
402 |
entry, a default article will be used (hard coded). |
|
403 |
Entries of the same date (for each article) are summed together and form a |
|
404 |
list entry in the long description of the item. |
|
405 |
|
|
406 |
The created delivery order object will be returnd but not saved. |
|
407 |
|
|
408 |
C<$sources> must be an array reference of C<SL::DB::TimeRecording> instances. |
|
409 |
|
|
410 |
C<%params> can include the following options: |
|
411 |
|
|
412 |
=over 2 |
|
413 |
|
|
414 |
=item C<attributes> |
|
415 |
|
|
416 |
An optional hash reference. If it exists then it is used to set |
|
417 |
attributes of the newly created delivery order object. |
|
418 |
|
|
419 |
=back |
|
420 |
|
|
304 | 421 |
=item C<sales_order> |
305 | 422 |
|
306 | 423 |
TODO: Describe sales_order |
Auch abrufbar als: Unified diff
Zeiterfassung: Lieferschein new_from_time_recording